diff --git a/.gitignore b/.gitignore index ad0f6885..56031b38 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,12 @@ coverage/ # Documentos internos de planejamento (não versionados) docs/planning/ +# Entregáveis locais (paletas/screenshots de review + apresentações de cliente): +# artefatos pesados/gerados, ficam fora do repo. +docs/design-options/ +docs/design-options.zip +docs/presentations/ + # Reports de teste (histórico só local — evita inflate do repo). # Mantém .gitkeep pra preservar diretórios vazios. Decisão registrada em # docs/planning/reorganization-proposal-2026-05-21.md §9 item 2. diff --git a/backend/apps/articles/apps.py b/backend/apps/articles/apps.py index d961740e..d5e74677 100644 --- a/backend/apps/articles/apps.py +++ b/backend/apps/articles/apps.py @@ -10,3 +10,8 @@ def ready(self) -> None: # Wire up signals that auto-notify newsletter subscribers # when an article is published. from . import signals # noqa: F401 + + # Registra o conversor de URL 'uslug' (slug unicode) uma única vez no + # boot do app. Antes articles/urls.py e comments/urls.py registravam + # cada um → 2ª chamada disparava RemovedInDjango60Warning. + from . import converters # noqa: F401 diff --git a/backend/apps/articles/converters.py b/backend/apps/articles/converters.py index de9449c1..e0edda4f 100644 --- a/backend/apps/articles/converters.py +++ b/backend/apps/articles/converters.py @@ -10,6 +10,7 @@ any script plus digits and underscore — a drop-in superset of Django's slug character class. """ +from django.urls import register_converter class UnicodeSlugConverter: @@ -20,3 +21,11 @@ def to_python(self, value: str) -> str: def to_url(self, value: str) -> str: return value + + +# Registra o conversor uma única vez, no import deste módulo. Antes articles/urls.py +# E comments/urls.py chamavam register_converter('uslug') cada → a 2ª chamada disparava +# RemovedInDjango60Warning (override de conversor já registrado). Um módulo Python roda +# só 1x (cache em sys.modules), então registrar aqui garante registro único, qualquer +# que seja a ordem de import dos urlconfs. +register_converter(UnicodeSlugConverter, 'uslug') diff --git a/backend/apps/articles/models.py b/backend/apps/articles/models.py index 8f82c0ab..58bd2b80 100644 --- a/backend/apps/articles/models.py +++ b/backend/apps/articles/models.py @@ -76,9 +76,20 @@ def _unique_slug(self) -> str: return slug def save(self, *args, **kwargs): + from django.db import transaction + if not self.slug: self.slug = self._unique_slug() - super().save(*args, **kwargs) + # Destaque único: marcar este como featured desmarca todos os outros. + # Padrão NYT/Substack — só 1 matéria ocupa o hero da home. Sem isso, + # Home.tsx (find(is_featured)) pegaria um featured arbitrário quando + # houvesse 2+. 2 writes (save + update) → atomic por ADR-012. + with transaction.atomic(): + super().save(*args, **kwargs) + if self.is_featured: + Article.objects.filter(is_featured=True).exclude(pk=self.pk).update( + is_featured=False + ) def __str__(self): return self.title diff --git a/backend/apps/articles/serializers.py b/backend/apps/articles/serializers.py index f074ba8a..16b2c6f6 100644 --- a/backend/apps/articles/serializers.py +++ b/backend/apps/articles/serializers.py @@ -34,6 +34,16 @@ class ArticleWriteSerializer(serializers.ModelSerializer): category_id = serializers.PrimaryKeyRelatedField( queryset=Category.objects.all(), source='category', required=False, allow_null=True ) + # Legenda obrigatória: todo artigo publicado precisa de crédito da capa + # (padrão G1/Folha — "Foto: Agência"). Model permite blank por + # retrocompat com artigos antigos, mas a escrita via API exige. + cover_caption = serializers.CharField( + max_length=300, required=True, allow_blank=False, + error_messages={ + 'blank': 'A legenda da capa é obrigatória (ex.: "Foto: Agência").', + 'required': 'A legenda da capa é obrigatória.', + }, + ) class Meta: model = Article @@ -42,6 +52,17 @@ class Meta: 'category_id', 'status', 'is_featured', ] + def validate(self, attrs): + # Imagem de capa obrigatória NA CRIAÇÃO — legenda sem imagem é + # incoerente. No update (partial), não força reenvio da imagem + # existente. self.instance é None em create. + is_create = self.instance is None + if is_create and not attrs.get('cover_image'): + raise serializers.ValidationError( + {'cover_image': 'A imagem de capa é obrigatória.'} + ) + return attrs + def create(self, validated_data): validated_data['author'] = self.context['request'].user return super().create(validated_data) diff --git a/backend/apps/articles/tests/test_views.py b/backend/apps/articles/tests/test_views.py index c827427e..0d36347b 100644 --- a/backend/apps/articles/tests/test_views.py +++ b/backend/apps/articles/tests/test_views.py @@ -54,6 +54,20 @@ def _make(author, status=Article.Status.PUBLISHED, title='Test Article', **kw): return _make +@pytest.fixture +def tiny_image(): + """SimpleUploadedFile com PNG 1x1 válido — ImageField (Pillow) exige + imagem real, não bytes arbitrários. Usado nos testes de create que agora + exigem cover_image obrigatória.""" + from django.core.files.uploadedfile import SimpleUploadedFile + import io + from PIL import Image + + buf = io.BytesIO() + Image.new('RGB', (16, 9), color=(20, 20, 76)).save(buf, format='PNG') + return SimpleUploadedFile('cover.png', buf.getvalue(), content_type='image/png') + + @pytest.fixture(autouse=True) def _clear_cache(): """View_count usa cache.add — limpa entre testes pra evitar contaminação.""" @@ -109,7 +123,7 @@ def test_list_articles_reader_does_not_see_drafts( ]) def test_create_article_permission_matrix( request, category, api_client, authed_client_factory, - fixture_name, expected_status, + tiny_image, fixture_name, expected_status, ): if fixture_name: user = request.getfixturevalue(fixture_name) @@ -123,13 +137,111 @@ def test_create_article_permission_matrix( 'body': 'A reasonably sized body for the test to pass any min-length validation.', 'category_id': category.id, 'status': 'draft', - }) + 'cover_caption': 'Foto: Agência Teste', # agora obrigatória + 'cover_image': tiny_image, # agora obrigatória no create + }, format='multipart') assert resp.status_code == expected_status, ( f'{fixture_name or "anon"} → expected {expected_status}, got {resp.status_code}: ' f'{resp.content[:200]}' ) +# ── Validação obrigatória: cover_caption + cover_image (create) ─────────────── + +def test_create_article_requires_cover_caption( + category, editor_user, authed_client_factory, tiny_image, +): + """Legenda da capa é obrigatória na criação (padrão G1/Folha — crédito + da foto). POST sem cover_caption → 400.""" + client = authed_client_factory(editor_user) + resp = client.post(ARTICLES_URL, data={ + 'title': 'Sem legenda', + 'excerpt': 'Excerpt.', + 'body': 'Body suficiente para passar validação.', + 'category_id': category.id, + 'status': 'draft', + 'cover_image': tiny_image, + # cover_caption ausente + }, format='multipart') + assert resp.status_code == 400 + assert 'cover_caption' in resp.json() + + +def test_create_article_rejects_blank_cover_caption( + category, editor_user, authed_client_factory, tiny_image, +): + """Legenda em branco (string vazia) também é rejeitada — não basta o + campo existir, precisa ter conteúdo.""" + client = authed_client_factory(editor_user) + resp = client.post(ARTICLES_URL, data={ + 'title': 'Legenda vazia', + 'excerpt': 'Excerpt.', + 'body': 'Body suficiente para passar validação.', + 'category_id': category.id, + 'status': 'draft', + 'cover_caption': ' ', # só espaços + 'cover_image': tiny_image, + }, format='multipart') + assert resp.status_code == 400 + assert 'cover_caption' in resp.json() + + +def test_create_article_requires_cover_image( + category, editor_user, authed_client_factory, +): + """Imagem de capa obrigatória na criação — legenda sem imagem é + incoerente. POST sem cover_image → 400.""" + client = authed_client_factory(editor_user) + resp = client.post(ARTICLES_URL, data={ + 'title': 'Sem capa', + 'excerpt': 'Excerpt.', + 'body': 'Body suficiente para passar validação.', + 'category_id': category.id, + 'status': 'draft', + 'cover_caption': 'Foto: Agência', + # cover_image ausente + }, format='multipart') + assert resp.status_code == 400 + assert 'cover_image' in resp.json() + + +def test_update_article_does_not_require_cover_image_resend( + make_article, editor_user, authed_client_factory, +): + """REGRESSÃO: editar artigo existente NÃO deve exigir reenvio da imagem + (a capa já existe). PATCH só do título deve passar.""" + art = make_article(editor_user, title='Para editar', cover_caption='Foto: X') + client = authed_client_factory(editor_user) + resp = client.patch( + f'/api/v1/articles/{art.slug}/', + data={'title': 'Título editado'}, + format='multipart', + ) + assert resp.status_code == 200, resp.content[:200] + + +# ── is_featured: destaque único ─────────────────────────────────────────────── + +def test_marking_article_featured_unsets_previous(make_article, editor_user): + """Padrão NYT/Substack — só 1 hero. Marcar um novo artigo como featured + desmarca o anterior automaticamente (model.save).""" + first = make_article(editor_user, title='Primeiro destaque', is_featured=True) + assert first.is_featured is True + + second = make_article(editor_user, title='Segundo destaque', is_featured=True) + + first.refresh_from_db() + assert first.is_featured is False, 'destaque antigo deveria ter sido desmarcado' + assert second.is_featured is True + + +def test_only_one_featured_after_multiple_marks(make_article, editor_user): + """Invariante dura: nunca mais de 1 featured no banco, mesmo após N marcações.""" + for i in range(5): + make_article(editor_user, title=f'Art {i}', is_featured=True) + assert Article.objects.filter(is_featured=True).count() == 1 + + # ── Update + Delete (object-level: dono ou admin) ───────────────────────────── def test_editor_can_update_own_article( @@ -150,15 +262,11 @@ def test_editor_can_update_own_article( def test_editor_cannot_update_other_editors_article( make_article, editor_user, db, authed_client_factory, ): - """Editor B não pode mexer no artigo de Editor A. Só dono ou admin. - - NOTA: a permission class IsPublisherOrReadOnly autoriza apenas a NÍVEL - DE VIEW (qualquer publisher pode PATCH); a restrição owner-only para - edição de outros é APENAS no frontend (ArticleAdminActions). Backend - hoje permite editor mexer no artigo de outro editor — débito conhecido - (A6/A9 §11.2 — refactor de permissões granulares). Este teste captura - o COMPORTAMENTO ATUAL: passa 200, NÃO 403. Quando IsOwnerOrAdmin entrar - no detail view, ajustar pra 403.""" + """SEGURANÇA: Editor B NÃO pode editar artigo de Editor A — só dono ou + admin/dev. Antes, IsPublisherOrReadOnly só restringia a nível de view + (qualquer publisher fazia PATCH) e a proteção owner-only existia APENAS + no frontend — trivial de burlar via curl. Fix: IsOwnerOrAdmin no + ArticleDetailView (object-level). Este teste é a regression do escalonamento.""" from apps.users.models import User other_editor = User.objects.create_user( username='outro.editor', email='outro@interpop.test', @@ -173,9 +281,29 @@ def test_editor_cannot_update_other_editors_article( data={'title': 'Edit by other editor'}, format='json', ) - # Comportamento atual: 200 (sem IsOwnerOrAdmin no detail). Quando o - # refactor entrar, virar 403 (e este teste passa a ser regression). - assert resp.status_code in (200, 403) + assert resp.status_code == 403, ( + 'ESCALONAMENTO: editor conseguiu editar artigo de outro editor. ' + 'IsOwnerOrAdmin deveria bloquear (403).' + ) + art.refresh_from_db() + assert art.title == 'Not Mine', 'título não deveria ter mudado' + + +def test_editor_cannot_delete_other_editors_article( + make_article, editor_user, db, authed_client_factory, +): + """Mesma proteção no DELETE — editor não apaga artigo alheio.""" + from apps.users.models import User + other = User.objects.create_user( + username='outro2.editor', email='outro2@interpop.test', + password='SenhaForte!2026', first_name='Outro2', last_name='Editor', + role=User.Role.EDITOR, + ) + art = make_article(other, title='Keep Mine') + client = authed_client_factory(editor_user) + resp = client.delete(f'/api/v1/articles/{art.slug}/') + assert resp.status_code == 403 + assert Article.objects.filter(pk=art.pk).exists() def test_admin_can_update_any_article( diff --git a/backend/apps/articles/urls.py b/backend/apps/articles/urls.py index 3d912368..f59baed0 100644 --- a/backend/apps/articles/urls.py +++ b/backend/apps/articles/urls.py @@ -1,9 +1,6 @@ -from django.urls import path, register_converter -from .converters import UnicodeSlugConverter +from django.urls import path from .views import ArticleDetailView, ArticleListView, ArticleViewCountView, CategoryListView -register_converter(UnicodeSlugConverter, 'uslug') - urlpatterns = [ path('categories/', CategoryListView.as_view(), name='category-list'), path('articles/', ArticleListView.as_view(), name='article-list'), diff --git a/backend/apps/articles/views.py b/backend/apps/articles/views.py index 67b133d5..ca64c661 100644 --- a/backend/apps/articles/views.py +++ b/backend/apps/articles/views.py @@ -7,7 +7,7 @@ from rest_framework.views import APIView from apps.audit.utils import get_client_ip -from apps.users.permissions import IsPublisherOrReadOnly +from apps.users.permissions import IsOwnerOrAdmin, IsPublisherOrReadOnly from .models import Article, Category from .serializers import ( ArticleDetailSerializer, @@ -36,9 +36,14 @@ class ArticleListView(generics.ListCreateAPIView): ordering_fields = ['published_at', 'view_count', 'created_at'] def get_queryset(self): + # .annotate(Count(...)) injeta GROUP BY → Django marca o queryset como + # "unordered" (QuerySet.ordered=False) mesmo com Meta.ordering, pois + # ordem default de query agregada é considerada não-confiável. Sem um + # order_by EXPLÍCITO o paginador do DRF dispara UnorderedObjectListWarning + # e pode paginar inconsistente. Repete a ordem do Meta de Article. qs = Article.objects.select_related('author', 'category').annotate( comment_count=Count('comments', filter=Q(comments__is_deleted=False)) - ) + ).order_by('-published_at', '-created_at') # Editorial team (admin + editor) enxerga drafts — convenção CMS # (WordPress/Ghost): toda equipe vê o estado editorial. Edição/exclusão # continua restrita ao próprio autor ou admin (regra no frontend + @@ -61,7 +66,11 @@ def perform_create(self, serializer): class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView): - permission_classes = [IsPublisherOrReadOnly] + # IsPublisherOrReadOnly: anon lê (GET), publisher escreve (nível de view). + # IsOwnerOrAdmin (object-level): só o AUTOR ou admin/dev pode PATCH/DELETE. + # Sem o segundo, qualquer editor editava/deletava artigo de QUALQUER outro + # editor via API (a restrição existia só no frontend — trivial de burlar). + permission_classes = [IsPublisherOrReadOnly, IsOwnerOrAdmin] lookup_field = 'slug' queryset = Article.objects.select_related('author', 'category') diff --git a/backend/apps/audit/tests/test_admin_metrics.py b/backend/apps/audit/tests/test_admin_metrics.py index 891228d2..90c359cf 100644 --- a/backend/apps/audit/tests/test_admin_metrics.py +++ b/backend/apps/audit/tests/test_admin_metrics.py @@ -308,6 +308,31 @@ def test_per_article_only_published( assert 'Visible Pub' in titles +def test_per_article_like_count_excludes_likes_on_deleted_comments( + admin_user, editor_user, reader_user, make_article, authed_client_factory, +): + """REGRESSÃO: like_count NÃO conta curtidas de comentários soft-deleted. + + comment_count já exclui deletados (filter is_deleted=False); like_count + precisa ser consistente, senão engagement_rate infla com curtidas em + conteúdo OCULTO (comment deletado some da tela mas seu like contava).""" + article = make_article(editor_user, title='DelLikes', view_count=100) + + alive = Comment.objects.create(article=article, author=reader_user, content='vivo') + CommentLike.objects.create(comment=alive, user=reader_user) # like válido + + dead = Comment.objects.create( + article=article, author=reader_user, content='morto', is_deleted=True, + ) + CommentLike.objects.create(comment=dead, user=editor_user) # like em comment oculto + + api = authed_client_factory(admin_user) + body = api.get(METRICS_URL).json() + row = next(a for a in body['per_article'] if a['title'] == 'DelLikes') + assert row['comment_count'] == 1 # só o "vivo" + assert row['like_count'] == 1 # like do comment deletado NÃO conta + + # ── category_breakdown ─────────────────────────────────────────────────────── diff --git a/backend/apps/audit/views.py b/backend/apps/audit/views.py index aca3c996..ef7b9f41 100644 --- a/backend/apps/audit/views.py +++ b/backend/apps/audit/views.py @@ -154,7 +154,15 @@ def get(self, request): filter=Q(comments__is_deleted=False), distinct=True, ), - like_count=Count('comments__likes', distinct=True), + # Mesmo filtro de is_deleted que comment_count: curtidas em + # comentários soft-deleted (ocultos da tela) não devem entrar + # no engagement. distinct evita a inflação do JOIN cartesiano + # comments × likes. + like_count=Count( + 'comments__likes', + filter=Q(comments__is_deleted=False), + distinct=True, + ), ) .order_by('-view_count') .values('slug', 'title', 'view_count', 'comment_count', 'like_count', 'published_at') diff --git a/backend/apps/comments/serializers.py b/backend/apps/comments/serializers.py index 593704f0..1b60a31f 100644 --- a/backend/apps/comments/serializers.py +++ b/backend/apps/comments/serializers.py @@ -35,11 +35,11 @@ class Meta: ] def get_replies_count(self, obj) -> int: - # replies is prefetched — no extra query - try: - return len(obj.replies.all()) - except Exception: - return 0 + # `replies` vem prefetchado (Prefetch em CommentListCreateView) — len() + # usa o cache, zero query extra. Sem try/except: se o prefetch quebrar + # num refactor, queremos o erro VISÍVEL, não um 0 silencioso mascarando + # bug (o except Exception engolia TudoDoesNotExist/AttributeError). + return len(obj.replies.all()) def validate_parent_id(self, value): if value is None: diff --git a/backend/apps/comments/urls.py b/backend/apps/comments/urls.py index 3581367d..f76525b7 100644 --- a/backend/apps/comments/urls.py +++ b/backend/apps/comments/urls.py @@ -1,11 +1,10 @@ -from django.urls import path, register_converter +from django.urls import path -from apps.articles.converters import UnicodeSlugConverter from .views import CommentDestroyView, CommentListCreateView, CommentLikeToggleView -# Same unicode-aware slug converter as the articles app — required so that -# article slugs containing accented characters (à, ç, é, …) resolve here too. -register_converter(UnicodeSlugConverter, 'uslug') +# O conversor 'uslug' (slug unicode, p/ slugs acentuados) é registrado uma única +# vez em ArticlesConfig.ready(). Registro é global no Django, então aqui basta +# usar — sem re-registrar (evita RemovedInDjango60Warning). urlpatterns = [ path('articles//comments/', CommentListCreateView.as_view(), name='comment-list'), diff --git a/backend/apps/moderation/serializers.py b/backend/apps/moderation/serializers.py index 05504f51..3dc0e5ee 100644 --- a/backend/apps/moderation/serializers.py +++ b/backend/apps/moderation/serializers.py @@ -6,10 +6,10 @@ class BanSerializer(serializers.ModelSerializer): user = UserPublicSerializer(read_only=True) - # Alvo pode ser usuário comum OU editor (admin pode banir editor abusivo). - # Admin e Dev nunca aparecem no queryset — hierarquia interna não é banível. + # Queryset do alvo é ATOR-AWARE (definido no __init__): dev (superadmin) + # pode banir admins; admin só user/editor. Dev nunca é alvo. user_id = serializers.PrimaryKeyRelatedField( - queryset=User.objects.filter(role__in=['user', 'editor'], is_banned=False), + queryset=User.objects.none(), write_only=True, source='user', ) @@ -26,13 +26,26 @@ class Meta: ] read_only_fields = ['id', 'banned_by', 'unbanned_by', 'created_at', 'is_active', 'unbanned_at'] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Camada 1 (queryset): só entram alvos baníveis pelo ATOR. Dev pode + # banir admin; demais atores, só user/editor. Sem ator (saída/sem + # contexto) usa o conjunto restritivo (fail-closed). + actor = getattr(self.context.get('request'), 'user', None) + roles = ['user', 'editor'] + if actor is not None and getattr(actor, 'is_authenticated', False) and actor.is_dev: + roles = ['user', 'editor', 'admin'] + self.fields['user_id'].queryset = User.objects.filter(role__in=roles, is_banned=False) + def validate_user_id(self, user): - # Defesa em profundidade: além do filtro de queryset, valida explicitamente - # que o alvo não é dev/admin. Mesmo se algum bug futuro alterar o queryset, - # esta checagem continua impedindo escalation de privilege. - if user.is_immune_to_ban: + # Camada 2 (defesa em profundidade): regra relacional explícita. Mesmo + # se o queryset for relaxado por bug futuro, isto barra escalation: + # dev é imune a todos; admin só é banível por dev. + actor = getattr(self.context.get('request'), 'user', None) + if not user.can_be_banned_by(actor): raise serializers.ValidationError( - 'Usuários com role Dev ou Admin são imunes a banimento por design.' + 'Você não pode banir este usuário: dev é imune a todos e admin ' + 'só pode ser banido por um dev.' ) if Ban.objects.filter(user=user, is_active=True).exists(): raise serializers.ValidationError('Usuário já está banido.') diff --git a/backend/apps/moderation/services.py b/backend/apps/moderation/services.py index 3fe2b8aa..b70055e8 100644 --- a/backend/apps/moderation/services.py +++ b/backend/apps/moderation/services.py @@ -12,6 +12,7 @@ """ from django.db import transaction from django.utils import timezone +from rest_framework.exceptions import PermissionDenied from apps.users.models import User from .models import Ban, BanRequest @@ -28,6 +29,13 @@ def ban_user(target: User, admin: User, reason: str, trigger_message: str = '') de cada ciclo no futuro, a saída é migrar `Ban.user` p/ ForeignKey + UniqueConstraint(condition=Q(is_active=True)). """ + # Camada 3 (defesa em profundidade, no service): última barreira da + # hierarquia de ban, mesmo se alguém chamar ban_user fora do serializer. + # dev imune a todos; admin só banível por dev; só admin/dev banem. + if not target.can_be_banned_by(admin): + raise PermissionDenied( + 'Hierarquia de banimento: dev é imune; admin só pode ser banido por um dev.' + ) ban, _created = Ban.objects.update_or_create( user=target, defaults={ @@ -45,6 +53,13 @@ def ban_user(target: User, admin: User, reason: str, trigger_message: str = '') @transaction.atomic def unban_user(ban: Ban, admin: User) -> Ban: + # Hierarquia também no unban: um admin comum NÃO desfaz o ban que um dev + # aplicou sobre um admin (senão a regra "admin só é controlado por dev" + # seria anulada pelo lado inverso). Mesma regra relacional do ban. + if not ban.user.can_be_unbanned_by(admin): + raise PermissionDenied( + 'Hierarquia: apenas um dev pode desbanir um admin.' + ) ban.is_active = False ban.unbanned_by = admin ban.unbanned_at = timezone.now() diff --git a/backend/apps/moderation/tasks.py b/backend/apps/moderation/tasks.py index 25248712..e773d979 100644 --- a/backend/apps/moderation/tasks.py +++ b/backend/apps/moderation/tasks.py @@ -40,12 +40,15 @@ def notify_admins_on_new_ban_request(self, ban_request_id: str) -> None: logger.warning('BanRequest %s not found — likely rejected before task ran', ban_request_id) return + # Inclui DEV (dono): por design dev é "admin++" e decide BanRequest. Sem + # ele, se o único superusuário for role=dev, NINGUÉM recebe a notificação. admins = list( - User.objects.filter(role=User.Role.ADMIN, is_active=True) - .values_list('email', flat=True) + User.objects.filter( + role__in=[User.Role.ADMIN, User.Role.DEV], is_active=True + ).values_list('email', flat=True) ) if not admins: - logger.info('No active admins to notify of BanRequest %s', ban_request_id) + logger.info('No active admins/devs to notify of BanRequest %s', ban_request_id) return requester = ( diff --git a/backend/apps/moderation/tests/test_ban_hierarchy.py b/backend/apps/moderation/tests/test_ban_hierarchy.py new file mode 100644 index 00000000..ee95350f --- /dev/null +++ b/backend/apps/moderation/tests/test_ban_hierarchy.py @@ -0,0 +1,152 @@ +""" +Hierarquia de banimento — dev é superadmin e o ÚNICO que bane admins. + +Regra (dev > admin > editor > user): + - dev: imune a todos (nunca é alvo de ban); + - admin: banível APENAS por um dev; + - editor/user: banível por admin ou dev; + - ninguém bane a si mesmo; só admin/dev banem diretamente. + +Cobre as 3 camadas: model (can_be_banned_by), endpoint (POST /bans/) e o +efeito real (is_banned). O contrário NÃO acontece: admin não bane dev nem +outro admin. +""" +from __future__ import annotations + +import pytest + +from apps.users.models import User + +BANS_URL = '/api/v1/moderation/bans/' + + +@pytest.fixture +def admin2(db) -> User: + return User.objects.create_user( + email='admin2@interpop.test', password='Interpop#2026', + username='admin2', first_name='Admin', last_name='Dois', role=User.Role.ADMIN, + ) + + +@pytest.fixture +def dev2(db) -> User: + return User.objects.create_user( + email='dev2@interpop.test', password='Interpop#2026', + username='dev2', first_name='Dev', last_name='Dois', role=User.Role.DEV, + ) + + +# ── Camada 1: model can_be_banned_by (matriz exaustiva) ────────────────────── + +@pytest.mark.django_db +def test_can_be_banned_by_matrix(dev_user, dev2, admin_user, admin2, editor_user, reader_user): + # dev é imune a TODOS (inclusive outro dev e ele mesmo) + assert dev_user.can_be_banned_by(admin_user) is False + assert dev_user.can_be_banned_by(dev2) is False + assert dev_user.can_be_banned_by(dev_user) is False + + # admin: SÓ por dev + assert admin_user.can_be_banned_by(dev_user) is True + assert admin_user.can_be_banned_by(admin2) is False # outro admin não + assert admin_user.can_be_banned_by(admin_user) is False # nem a si mesmo + assert admin_user.can_be_banned_by(editor_user) is False + + # editor / user: por admin OU dev; não por editor; nem None + for target in (editor_user, reader_user): + assert target.can_be_banned_by(admin_user) is True + assert target.can_be_banned_by(dev_user) is True + assert target.can_be_banned_by(editor_user) is False + assert target.can_be_banned_by(None) is False + + +# ── Camada 2+3: endpoint POST /bans/ (full stack) ──────────────────────────── + +@pytest.mark.django_db +def test_dev_can_ban_admin(dev_user, admin_user, authed_client_factory): + api = authed_client_factory(dev_user) + resp = api.post(BANS_URL, {'user_id': str(admin_user.id), 'reason': 'abuso de poder'}, format='json') + assert resp.status_code == 201, resp.content + admin_user.refresh_from_db() + assert admin_user.is_banned is True + + +@pytest.mark.django_db +def test_admin_cannot_ban_another_admin(admin_user, admin2, authed_client_factory): + api = authed_client_factory(admin_user) + resp = api.post(BANS_URL, {'user_id': str(admin2.id), 'reason': 'x'}, format='json') + assert resp.status_code == 400 + admin2.refresh_from_db() + assert admin2.is_banned is False + + +@pytest.mark.django_db +def test_admin_cannot_ban_dev(admin_user, dev_user, authed_client_factory): + api = authed_client_factory(admin_user) + resp = api.post(BANS_URL, {'user_id': str(dev_user.id), 'reason': 'x'}, format='json') + assert resp.status_code == 400 + dev_user.refresh_from_db() + assert dev_user.is_banned is False + + +@pytest.mark.django_db +def test_dev_cannot_ban_another_dev(dev_user, dev2, authed_client_factory): + api = authed_client_factory(dev_user) + resp = api.post(BANS_URL, {'user_id': str(dev2.id), 'reason': 'x'}, format='json') + assert resp.status_code == 400 + dev2.refresh_from_db() + assert dev2.is_banned is False + + +@pytest.mark.django_db +def test_admin_can_still_ban_editor(admin_user, editor_user, authed_client_factory): + """Regressão: comportamento antigo (admin bane editor/user) preservado.""" + api = authed_client_factory(admin_user) + resp = api.post(BANS_URL, {'user_id': str(editor_user.id), 'reason': 'spam'}, format='json') + assert resp.status_code == 201, resp.content + editor_user.refresh_from_db() + assert editor_user.is_banned is True + + +@pytest.mark.django_db +def test_editor_cannot_reach_ban_endpoint(editor_user, reader_user, authed_client_factory): + """Editor não bane direto (IsAdminUser) — só solicita via BanRequest.""" + api = authed_client_factory(editor_user) + resp = api.post(BANS_URL, {'user_id': str(reader_user.id), 'reason': 'x'}, format='json') + assert resp.status_code == 403 + + +# ── UNBAN: a hierarquia vale também no sentido inverso ─────────────────────── + +@pytest.mark.django_db +def test_admin_cannot_unban_dev_placed_ban_on_admin(dev_user, admin_user, admin2, authed_client_factory): + """Dev bane admin → um admin comum NÃO pode desfazer (DELETE → 403).""" + from apps.moderation.services import ban_user + ban = ban_user(target=admin_user, admin=dev_user, reason='abuso') + api = authed_client_factory(admin2) + resp = api.delete(f'{BANS_URL}{ban.pk}/') + assert resp.status_code == 403 + admin_user.refresh_from_db() + assert admin_user.is_banned is True + + +@pytest.mark.django_db +def test_dev_can_unban_admin(dev_user, admin_user, authed_client_factory): + from apps.moderation.services import ban_user + ban = ban_user(target=admin_user, admin=dev_user, reason='abuso') + api = authed_client_factory(dev_user) + resp = api.delete(f'{BANS_URL}{ban.pk}/') + assert resp.status_code in (200, 204), resp.content + admin_user.refresh_from_db() + assert admin_user.is_banned is False + + +@pytest.mark.django_db +def test_admin_can_unban_editor(admin_user, editor_user, authed_client_factory): + """Regressão: admin segue podendo desbanir editor/user.""" + from apps.moderation.services import ban_user + ban = ban_user(target=editor_user, admin=admin_user, reason='spam') + api = authed_client_factory(admin_user) + resp = api.delete(f'{BANS_URL}{ban.pk}/') + assert resp.status_code in (200, 204), resp.content + editor_user.refresh_from_db() + assert editor_user.is_banned is False diff --git a/backend/apps/moderation/tests/test_serializers.py b/backend/apps/moderation/tests/test_serializers.py index cab5a401..91089256 100644 --- a/backend/apps/moderation/tests/test_serializers.py +++ b/backend/apps/moderation/tests/test_serializers.py @@ -1,93 +1,116 @@ """ Testes de defesa em profundidade dos serializers de moderation. -Por que estes testes existem: a hierarquia dev/admin imune a ban é -fundacional pra segurança. O modelo tem 3 camadas de defesa contra -banimento acidental de privilegiados: - - 1. Queryset do PrimaryKeyRelatedField filtra `role__in=['user','editor']` - — não permite nem sequer SELECIONAR um admin/dev. - 2. validate_user_id checa `user.is_immune_to_ban` explicitamente — - mesmo se algum bug futuro relaxar o queryset, esta valida ainda - bloqueia. - 3. ban_user service (em test_services.py) testa o caminho feliz. - -Quebrar QUALQUER uma das camadas sem quebrar este teste = regression -silenciosa que só aparece quando alguém efetivamente banir um admin -por engano em produção. Indesejável. +Hierarquia de banimento (dev > admin > editor > user) — agora RELACIONAL no +ban direto: + - dev: imune a todos (superadmin, nunca é alvo); + - admin: banível APENAS por um dev; + - editor/user: banível por admin ou dev. + +3 camadas de defesa no ban DIRETO (BanSerializer): + 1. Queryset ator-aware do PrimaryKeyRelatedField — só lista alvos que o ATOR + pode banir (dev vê admins; admin não). + 2. validate_user_id chama user.can_be_banned_by(actor) — barra escalation + mesmo se a camada 1 for relaxada. + 3. service ban_user re-checa (test_services / test_ban_hierarchy). + +O BanRequest (editor solicita) continua restrito a user/editor via +is_immune_to_ban — editor não solicita ban de dev/admin. """ from __future__ import annotations +from types import SimpleNamespace + import pytest from apps.moderation.serializers import BanRequestSerializer, BanSerializer -from apps.moderation.models import Ban -# ── Camada 1: queryset filtra dev/admin ────────────────────────────────────── +def _ctx(actor): + """Contexto mínimo de serializer com o ator (request.user).""" + return {'request': SimpleNamespace(user=actor)} -@pytest.mark.parametrize('fixture_name', ['dev_user', 'admin_user']) -def test_ban_serializer_queryset_excludes_immune_users(request, fixture_name): - """PrimaryKeyRelatedField com queryset role__in=['user','editor'] - deveria rejeitar dev/admin já no parsing do payload.""" - target = request.getfixturevalue(fixture_name) - serializer = BanSerializer(data={ - 'user_id': str(target.id), - 'reason': 'test', - }) - assert not serializer.is_valid() - assert 'user_id' in serializer.errors +# ── Camada 1: queryset ator-aware ──────────────────────────────────────────── + +@pytest.mark.parametrize('actor_fixture', ['admin_user', 'dev_user']) +def test_queryset_excludes_dev_target_for_any_actor(request, actor_fixture, dev_user): + """Dev NUNCA é alvo, nem para um ator dev.""" + actor = request.getfixturevalue(actor_fixture) + s = BanSerializer(data={'user_id': str(dev_user.id), 'reason': 'x'}, context=_ctx(actor)) + assert not s.is_valid() + assert 'user_id' in s.errors -# ── Camada 2: validate_user_id explícito ───────────────────────────────────── -def test_validate_user_id_message_for_immune(reader_user, admin_user, mocker): - """Mesmo se camada 1 falhasse (queryset relaxado), validate_user_id - rejeitaria com mensagem clara. Simulamos relaxamento do queryset via - mock e confirmamos que o validate explícito vence.""" +def test_queryset_excludes_admin_target_for_admin_actor(admin_user, db): + """Admin não pode nem selecionar outro admin (só dev pode).""" from apps.users.models import User - serializer = BanSerializer(data={'user_id': str(admin_user.id), 'reason': 'x'}) - # Bypass camada 1 — substitui o queryset por TODOS os users - serializer.fields['user_id'].queryset = User.objects.all() + target = User.objects.create_user( + email='admin2@interpop.test', password='Interpop#2026', + username='admin2', first_name='Admin', last_name='Dois', role=User.Role.ADMIN, + ) + s = BanSerializer(data={'user_id': str(target.id), 'reason': 'x'}, context=_ctx(admin_user)) + assert not s.is_valid() + assert 'user_id' in s.errors - assert not serializer.is_valid() - msg = str(serializer.errors['user_id']) - assert 'Dev' in msg or 'Admin' in msg or 'imun' in msg.lower(), ( - 'Mensagem deveria explicar que dev/admin são imunes. Recebido: ' - f'{msg}' + +def test_queryset_includes_admin_target_for_dev_actor(dev_user, admin_user): + """Dev (superadmin) PODE banir admin — admin aparece no queryset.""" + s = BanSerializer(data={'user_id': str(admin_user.id), 'reason': 'abuso'}, context=_ctx(dev_user)) + assert s.is_valid(), s.errors + + +# ── Camada 2: validate_user_id (relacional, defesa em profundidade) ────────── + +def test_validate_blocks_admin_banning_admin_even_if_queryset_relaxed(admin_user, db): + """Mesmo relaxando o queryset, validate barra admin→admin.""" + from apps.users.models import User + target = User.objects.create_user( + email='admin3@interpop.test', password='Interpop#2026', + username='admin3', first_name='Admin', last_name='Três', role=User.Role.ADMIN, ) + s = BanSerializer(data={'user_id': str(target.id), 'reason': 'x'}, context=_ctx(admin_user)) + s.fields['user_id'].queryset = User.objects.all() # bypass camada 1 + assert not s.is_valid() + msg = str(s.errors['user_id']).lower() + assert 'dev' in msg or 'imune' in msg -# ── Casos felizes: editor e reader são alvos legítimos ─────────────────────── +def test_validate_blocks_banning_dev_even_for_dev_actor(dev_user): + """Dev é imune a todos — inclusive a outro dev / a si mesmo.""" + from apps.users.models import User + s = BanSerializer(data={'user_id': str(dev_user.id), 'reason': 'x'}, context=_ctx(dev_user)) + s.fields['user_id'].queryset = User.objects.all() + assert not s.is_valid() + assert 'user_id' in s.errors -@pytest.mark.parametrize('fixture_name', ['editor_user', 'reader_user']) -def test_ban_serializer_accepts_editor_and_reader(request, fixture_name): - target = request.getfixturevalue(fixture_name) - serializer = BanSerializer(data={ - 'user_id': str(target.id), - 'reason': 'spam de fato', - }) - assert serializer.is_valid(), serializer.errors + +# ── Casos felizes: editor e reader são alvos legítimos (ator admin) ────────── + +@pytest.mark.parametrize('target_fixture', ['editor_user', 'reader_user']) +def test_admin_can_target_editor_and_reader(request, target_fixture, admin_user): + target = request.getfixturevalue(target_fixture) + s = BanSerializer( + data={'user_id': str(target.id), 'reason': 'spam de fato'}, + context=_ctx(admin_user), + ) + assert s.is_valid(), s.errors -# ── Caso defensivo: usuário já banido não pode ser banido duas vezes ───────── +# ── Defensivo: usuário já banido não pode ser banido duas vezes ────────────── def test_ban_serializer_rejects_already_banned_user(reader_user, admin_user): - """Ban OneToOne já garante constraint, mas validate dá mensagem - amigável antes de o constraint estourar.""" from apps.moderation.services import ban_user ban_user(target=reader_user, admin=admin_user, reason='first') - - serializer = BanSerializer(data={ - 'user_id': str(reader_user.id), - 'reason': 'second', - }) - # Queryset filtra `is_banned=False` — então user banido nem aparece. - # Camada 1 ataca primeiro. - assert not serializer.is_valid() + s = BanSerializer( + data={'user_id': str(reader_user.id), 'reason': 'second'}, + context=_ctx(admin_user), + ) + # Queryset filtra is_banned=False → usuário banido nem aparece (camada 1). + assert not s.is_valid() -# ── BanRequestSerializer (mesma lógica de imunidade) ───────────────────────── +# ── BanRequestSerializer: editor não solicita ban de dev/admin (inalterado) ── @pytest.mark.parametrize('fixture_name', ['dev_user', 'admin_user']) def test_ban_request_serializer_rejects_immune_target(request, fixture_name): @@ -100,16 +123,9 @@ def test_ban_request_serializer_rejects_immune_target(request, fixture_name): assert 'target_id' in serializer.errors -def test_ban_request_serializer_rejects_duplicate_pending( - reader_user, editor_user, -): - """2 redatores não podem ter request pending pra mesmo alvo — - serializer bloqueia antes do create.""" +def test_ban_request_serializer_rejects_duplicate_pending(reader_user, editor_user): from apps.moderation.models import BanRequest - BanRequest.objects.create( - target=reader_user, requested_by=editor_user, reason='spam', - ) - + BanRequest.objects.create(target=reader_user, requested_by=editor_user, reason='spam') serializer = BanRequestSerializer(data={ 'target_id': str(reader_user.id), 'reason': 'spam 2', diff --git a/backend/apps/moderation/tests/test_services.py b/backend/apps/moderation/tests/test_services.py index 8ca51791..2770c2a0 100644 --- a/backend/apps/moderation/tests/test_services.py +++ b/backend/apps/moderation/tests/test_services.py @@ -15,6 +15,7 @@ from __future__ import annotations import pytest +from rest_framework.exceptions import PermissionDenied from apps.moderation.models import Ban, BanRequest from apps.moderation.services import ( @@ -23,6 +24,15 @@ reject_ban_request, unban_user, ) +from apps.users.models import User + + +def _make_admin(suffix: str) -> User: + return User.objects.create_user( + email=f'admin_{suffix}@interpop.test', password='Interpop#2026', + username=f'admin_{suffix}', first_name='Admin', last_name=suffix.title(), + role=User.Role.ADMIN, + ) # ── ban_user ─────────────────────────────────────────────────────────────────── @@ -116,6 +126,56 @@ def test_unban_user_clears_flag_and_records_history(reader_user, admin_user): assert result.unbanned_at is not None +# ── Hierarquia no service (camada 3, independente de serializer/endpoint) ─────── + +def test_ban_user_blocks_admin_banning_admin(admin_user, db): + other_admin = _make_admin('alvo') + with pytest.raises(PermissionDenied): + ban_user(target=other_admin, admin=admin_user, reason='x') + other_admin.refresh_from_db() + assert other_admin.is_banned is False + assert not Ban.objects.filter(user=other_admin).exists() + + +def test_ban_user_blocks_banning_dev(dev_user, admin_user): + with pytest.raises(PermissionDenied): + ban_user(target=dev_user, admin=admin_user, reason='x') + dev_user.refresh_from_db() + assert dev_user.is_banned is False + + +def test_ban_user_allows_dev_banning_admin(dev_user, admin_user): + ban = ban_user(target=admin_user, admin=dev_user, reason='abuso') + admin_user.refresh_from_db() + assert ban.is_active is True + assert admin_user.is_banned is True + + +def test_unban_user_blocks_admin_undoing_dev_ban_on_admin(dev_user, admin_user, db): + """Um admin comum NÃO pode desfazer o ban que um dev pôs num admin.""" + other_admin = _make_admin('punido') + ban = ban_user(target=other_admin, admin=dev_user, reason='abuso') # dev bane admin + with pytest.raises(PermissionDenied): + unban_user(ban, admin=admin_user) # admin tenta desfazer + other_admin.refresh_from_db() + assert other_admin.is_banned is True # continua banido + + +def test_unban_user_allows_dev_undoing_ban_on_admin(dev_user, admin_user): + ban = ban_user(target=admin_user, admin=dev_user, reason='abuso') + unban_user(ban, admin=dev_user) + admin_user.refresh_from_db() + assert admin_user.is_banned is False + + +def test_unban_user_allows_admin_undoing_editor_ban(admin_user, editor_user): + """Editor/user não é exclusivo de dev — admin pode desbanir normalmente.""" + ban = ban_user(target=editor_user, admin=admin_user, reason='spam') + unban_user(ban, admin=admin_user) + editor_user.refresh_from_db() + assert editor_user.is_banned is False + + # ── BanRequest workflow ─────────────────────────────────────────────────────── def test_ban_request_approve_creates_ban_and_marks_request( diff --git a/backend/apps/moderation/tests/test_tasks.py b/backend/apps/moderation/tests/test_tasks.py new file mode 100644 index 00000000..42ebfab9 --- /dev/null +++ b/backend/apps/moderation/tests/test_tasks.py @@ -0,0 +1,45 @@ +""" +Tests da task notify_admins_on_new_ban_request. + +Regressão: a query de destinatários filtrava só role=ADMIN, excluindo DEV +(dono, que é admin++ e decide BanRequests). Se o único superusuário fosse +role=dev, ninguém recebia a notificação de solicitação de ban. +""" +from __future__ import annotations + +import pytest +from django.core import mail + +from apps.moderation.models import BanRequest +from apps.moderation.tasks import notify_admins_on_new_ban_request + + +@pytest.mark.django_db +def test_notification_includes_dev(dev_user, editor_user, reader_user): + """Dev (dono) deve receber a notificação de novo BanRequest.""" + br = BanRequest.objects.create( + target=reader_user, requested_by=editor_user, reason='spam recorrente', + ) + mail.outbox.clear() # descarta o que o signal post_save já enviou + notify_admins_on_new_ban_request(str(br.id)) + + recipients = [addr for m in mail.outbox for addr in m.to] + assert dev_user.email in recipients, ( + f'dev não notificado. Destinatários: {recipients}' + ) + + +@pytest.mark.django_db +def test_notification_includes_both_admin_and_dev( + dev_user, admin_user, editor_user, reader_user, +): + """Admin E dev recebem (ambos decidem BanRequest).""" + br = BanRequest.objects.create( + target=reader_user, requested_by=editor_user, reason='conteúdo abusivo', + ) + mail.outbox.clear() + notify_admins_on_new_ban_request(str(br.id)) + + recipients = [addr for m in mail.outbox for addr in m.to] + assert dev_user.email in recipients + assert admin_user.email in recipients diff --git a/backend/apps/moderation/views.py b/backend/apps/moderation/views.py index 9806945b..7c3804d3 100644 --- a/backend/apps/moderation/views.py +++ b/backend/apps/moderation/views.py @@ -16,7 +16,7 @@ # ─── Ban direto (admin only) ───────────────────────────────────────────── class BanListCreateView(generics.ListCreateAPIView): - permission_classes = [IsAdminUser] + permission_classes = [IsAdminUser, IsNotBanned] serializer_class = BanSerializer queryset = Ban.objects.filter(is_active=True).select_related( 'user', 'banned_by', 'unbanned_by' @@ -37,7 +37,7 @@ def create(self, request, *args, **kwargs): class BanDestroyView(generics.RetrieveDestroyAPIView): - permission_classes = [IsAdminUser] + permission_classes = [IsAdminUser, IsNotBanned] serializer_class = BanSerializer queryset = Ban.objects.filter(is_active=True).select_related('user', 'banned_by') lookup_field = 'pk' @@ -79,7 +79,7 @@ class BanRequestDecideView(APIView): Body: { "action": "approve" | "reject", "decision_note": "..." (opcional) } Aprovar cria Ban real (User.is_banned=True). Rejeitar só muda status. """ - permission_classes = [IsAdminUser] + permission_classes = [IsAdminUser, IsNotBanned] def post(self, request, pk): try: diff --git a/backend/apps/search/README.md b/backend/apps/search/README.md new file mode 100644 index 00000000..3997b7f8 --- /dev/null +++ b/backend/apps/search/README.md @@ -0,0 +1,101 @@ +# apps.search + +App da **busca editorial full-text** do Interpop. Esta pasta é a **fonte de verdade da camada DB** da feature (Fase 1 do `DESIGN.md §6`). Camadas de serviço (`SearchService`), view (`SearchView`), serializers, signals reais e frontend ficam para fases subsequentes. + +## Resumo arquitetural (decisões locked) + +| Decisão | ADR | Implementação | +|---|---|---| +| Tabela paralela `search_index` (read-projection) | ADR-015, ADR-016 | Migration `0001_initial` | +| FTS pt-BR via `CONFIGURATION pt_unaccent` (IMMUTABLE preservado) | **ADR-019** | Migration `0001_initial` + função `articles_search_config` | +| Trigger SQL = fonte de verdade da sincronia | **ADR-018** | Migration `0003_search_triggers` | +| Signal Python apenas invalida cache Redis | ADR-018 | `signals.py` (stub — Fase 2) | +| Composite indexes parciais + covering | **ADR-030-DB** | Migration `0002_search_indexes` (`atomic=False`) | +| Vacuum tuning GIN agressivo | **ADR-034** | Migration `0004_search_vacuum_tuning` | +| Fallback SQLite-dev (sem FTS pt-BR) | ADR-020 | Guards `connection.vendor == 'postgresql'` | + +## Estrutura + +``` +apps/search/ +├── apps.py # AppConfig — ready() registra signals +├── models.py # SearchIndex (managed=False), SearchLog (managed=False) +├── services.py # STUB Fase 2 (SearchService.query) +├── dto.py # STUB Fase 2 (QuerySpec, SearchResultPage) +├── signals.py # STUB Fase 2 (cache invalidation) +├── migrations/ +│ ├── 0001_initial.py # ext + config + function + tabelas +│ ├── 0002_search_indexes.py # CONCURRENTLY + partial + covering (atomic=False) +│ ├── 0003_search_triggers.py # trg_articles_sync_search + 2 triggers +│ └── 0004_search_vacuum_tuning.py # ALTER INDEX/TABLE SET +├── management/commands/ # reindex_search vem na Fase 2 (T30.1.6b) +├── tests/ +│ └── test_migrations.py +└── README.md +``` + +## Fallback SQLite-dev (ADR-020) + +Em ambiente local (`config.settings.development`), o engine padrão é SQLite. +SQLite não suporta: + +- `CREATE EXTENSION unaccent` +- `CREATE TEXT SEARCH CONFIGURATION` +- `to_tsvector` / `tsvector` nativos +- Trigger PL/pgSQL (suporta apenas trigger SQL muito limitado) +- `CREATE INDEX CONCURRENTLY` +- Partial indexes com expressões complexas +- `ALTER INDEX ... SET (fastupdate = ...)` + +**Estratégia** (DESIGN §3.6 + ADR-020): + +1. Toda migration desta pasta guarda as operações Postgres-only com: + + ```python + if connection.vendor == 'postgresql': + schema_editor.execute(POSTGRES_SQL) + ``` + + Em SQLite, a migration cria **apenas o esqueleto mínimo das tabelas** (sem `search_vector` real, sem trigger, sem GIN). Isso garante que `migrate` não trava, e modelos com `managed=False` continuam mapeáveis. + +2. O `SearchService` (Fase 2) faz fallback `__icontains` em SQLite — qualidade de resultado pior, mas evita drift dev/prod no fluxo de desenvolvimento. + +3. **CI usa Postgres** (sempre). Testes marcados com `pytest.mark.requires_postgres` pulam em SQLite local. + +## Como rodar as migrations + +```bash +# Sync deps +cd backend && uv sync + +# Aplicar todas as migrations de search +uv run python manage.py migrate search + +# Em Postgres, isso cria: +# - extension unaccent +# - configuration pt_unaccent +# - função articles_search_config(text) +# - tabela search_index com 4 índices (1 GIN + 2 partial/covering + 1 BTree) +# - trigger trg_articles_sync_search em apps.articles.Article +# - ALTER TABLE/INDEX com vacuum tuning agressivo + +# Em SQLite (dev local), isso cria apenas o esqueleto das tabelas +# search_index e search_log, sem extensions, sem configuration, sem triggers. +``` + +## Provisão Postgres em produção + +`CREATE EXTENSION unaccent` exige `SUPERUSER`. Documentação de provisão na +Hostinger KVM 1 → `docs/runbooks/setup-postgres-extensions.md` (TX-13, ainda +não escrito). Em ambientes gerenciados, executar a primeira vez como usuário +admin do Postgres antes de rodar as migrations Django como usuário da app. + +## Referências + +- `docs/specs/busca-editorial/DESIGN.md §2.2` (database arch) +- `docs/specs/busca-editorial/_specialist-outputs/01-database-architect.md` +- ADR-018 (trigger SQL fonte de verdade) +- ADR-019 (pt_unaccent CONFIGURATION) +- ADR-020 (SQLite dev fallback) +- ADR-030-DB (composite indexes parciais/covering) +- ADR-034 (vacuum tuning GIN) diff --git a/backend/apps/search/__init__.py b/backend/apps/search/__init__.py new file mode 100644 index 00000000..3a3abe02 --- /dev/null +++ b/backend/apps/search/__init__.py @@ -0,0 +1,9 @@ +"""apps.search — Postgres FTS read-projection for the editorial search. + +Bounded context (ADR-015): owns SearchIndex (read-projection of Article) and +SearchService. SearchIndex is kept in sync with Article via Postgres trigger +(fonte de verdade — ADR-018); the Django signal in :mod:`apps.search.signals` +only invalidates the Redis cache. +""" + +default_app_config = 'apps.search.apps.SearchConfig' diff --git a/backend/apps/search/apps.py b/backend/apps/search/apps.py new file mode 100644 index 00000000..6c378cb1 --- /dev/null +++ b/backend/apps/search/apps.py @@ -0,0 +1,21 @@ +from django.apps import AppConfig + + +class SearchConfig(AppConfig): + """Configuração do app de busca editorial. + + ``default_auto_field`` segue o padrão do projeto (BigAutoField), embora + ``SearchIndex`` use chave primária composta por ``article_id UUID`` (ver + migration ``0001_initial``). A escolha de BigAutoField é apenas o default + para qualquer model auxiliar que venha a surgir (ex.: ``SearchLog``). + """ + + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.search' + verbose_name = 'Busca editorial' + + def ready(self) -> None: + # Importa signals para registrá-los no boot do app. + # NOTA: o signal só faz invalidação de cache Redis. A sincronia + # Article → SearchIndex é feita por TRIGGER POSTGRES (ADR-018). + from . import signals # noqa: F401 diff --git a/backend/apps/search/cache.py b/backend/apps/search/cache.py new file mode 100644 index 00000000..a2c10092 --- /dev/null +++ b/backend/apps/search/cache.py @@ -0,0 +1,133 @@ +"""Cache helpers da busca — separação por ``auth_tier`` (ADR-037 / H-04). + +SECURITY-REVIEW H-04 (CWE-524): cache key SEM ``auth_tier`` mistura +respostas de usuário anônimo e autenticado. Esta camada garante +KEYS distintas por tier mesmo para o mesmo ``QuerySpec``. + +Formato da key:: + + search:v1:: + +- ``search:v1:`` — prefix versionado, consumido pelo + ``cache.delete_pattern('search:v1:*')`` no signal de invalidação + (ADR-037). +- ```` ∈ {``anon``, ``user``} — separa pools. +- ```` — hash hex da canonical query string (q normalizado + + filtros + cursor + per_page). + +Invariante (testada): mesma input + tier → mesma key; tier diferente → key +diferente; tier desconhecido → ``ValueError`` (não fallback silencioso). +""" +from __future__ import annotations + +import hashlib +import json +import logging +from typing import Final, Literal + +from django.core.cache import cache + +from .dto import QuerySpec +from .utils import normalize_search_text + + +logger = logging.getLogger('interpop.search.cache') + + +CACHE_KEY_PREFIX: Final[str] = 'search:v1:' + +AuthTier = Literal['anon', 'user'] +_VALID_TIERS: Final[frozenset[AuthTier]] = frozenset({'anon', 'user'}) + + +def canonical_query_string(spec: QuerySpec) -> str: + """Serializa a parte "stable" do spec para hashing. + + NÃO inclui ``cursor`` aqui — o cursor é incluído explicitamente em + :func:`build_cache_key` (porque cada página da mesma query deve ter + cache key própria, mas o cursor não faz parte do "shape" semântico + da busca). + + Saída JSON ordenada por chave (determinístico cross-versão Python). + """ + payload = { + 'q': normalize_search_text(spec.q), + 'author_id': str(spec.author_id) if spec.author_id else None, + 'category_id': spec.category_id, + 'de': spec.de.isoformat() if spec.de else None, + 'ate': spec.ate.isoformat() if spec.ate else None, + 'per_page': spec.per_page, + } + return json.dumps(payload, sort_keys=True, separators=(',', ':')) + + +def build_cache_key(spec: QuerySpec, *, auth_tier: AuthTier) -> str: + """Constrói a cache key para ``(spec, auth_tier)``. + + Args: + spec: :class:`QuerySpec` validado. + auth_tier: ``'anon'`` para anônimo, ``'user'`` para autenticado. + **Não há outros valores** — admin/editor não têm tier separado + (busca é leitura pública sem dados personalizados; ADR-037 + invariante "response function-pure de (q, filters, cursor)"). + + Returns: + ``search:v1::``. + + Raises: + ValueError: tier fora de ``{'anon', 'user'}`` — fail-fast em vez + de cair em pool comum silenciosamente (defesa H-04). + """ + if auth_tier not in _VALID_TIERS: + raise ValueError( + f"auth_tier inválido: {auth_tier!r}. " + f"Esperado um de {sorted(_VALID_TIERS)}. " + "Tier desconhecido em fallback silencioso = vetor H-04." + ) + # Inclui o cursor aqui — páginas distintas da mesma query merecem + # cache keys distintas (cada página = response shape distinto). + payload_str = canonical_query_string(spec) + cursor_part = spec.cursor or '' + digest = hashlib.sha256( + (payload_str + '|cursor=' + cursor_part).encode('utf-8'), + ).hexdigest() + return f'{CACHE_KEY_PREFIX}{auth_tier}:{digest}' + + +# ── Invalidação proativa (signal post_save Article) ────────────────────────── + + +def invalidate_all_search_cache() -> int: + """Apaga TODAS as chaves ``search:v1:*``. + + Chamada do signal post_save / post_delete em Article (T30.1.5c) — Inv: + o signal NUNCA escreve em ``search_index`` (trigger SQL é fonte de + verdade, ADR-018); ele APENAS invalida cache. + + Backend Redis (django-redis) suporta ``delete_pattern``. Em LocMemCache + (dev / fallback) não há pattern delete — fazemos ``cache.clear()`` + como degradação aceita: dev tem traffic baixo, cache é per-worker, e + a corretude (não servir stale) > performance. + + Returns: + Número de chaves removidas, ou ``-1`` em fallback (LocMemCache não + reporta count). + """ + delete_pattern = getattr(cache, 'delete_pattern', None) + if delete_pattern is not None: + try: + removed = delete_pattern(f'{CACHE_KEY_PREFIX}*') + logger.info( + 'search.cache.invalidate.pattern', + extra={'removed': removed}, + ) + return int(removed) if removed is not None else 0 + except Exception: # pragma: no cover — Redis transient + logger.exception('search.cache.invalidate.pattern_failed') + # cai no fallback + # Fallback: clear total (dev, LocMemCache). Aceita o trade-off de + # invalidar TODO o cache (não só search:v1:*) — em prod com Redis, + # delete_pattern é cirúrgico. + cache.clear() + logger.info('search.cache.invalidate.fallback_clear') + return -1 diff --git a/backend/apps/search/cursors.py b/backend/apps/search/cursors.py new file mode 100644 index 00000000..62cf99f4 --- /dev/null +++ b/backend/apps/search/cursors.py @@ -0,0 +1,139 @@ +"""Cursor HMAC para paginação keyset (Inv #5, #6, #9 algorithms). + +Por que HMAC e não JWT/Fernet: + + - Cursor não tem PII; só estado (score, published_at, article_id, depth). + - HMAC SHA256 é mais simples, sem expiração necessária no MVP + (rotação de secret invalida tudo automaticamente — aceito). + - ``hmac.compare_digest`` (timing-safe) evita oracle por tempo de + comparação (L-04 do SECURITY-REVIEW). + +Formato wire:: + + . + +JSON payload schema:: + + { + "s": , # score com ROUND(6) já aplicado + "p": "2026-05-01T12:00:00+00:00", + "i": "", + "d": # depth (cap 50 — Inv #9) + } + +Decisões: + - Inv #5: assinatura inválida → :exc:`InvalidCursorError` (view traduz para 400) + - Inv #6: score arredondado em 6 casas (simétrico com SELECT da CTE) + - Inv #9: ``depth`` validado no decode (rejeita > settings.SEARCH_MAX_PAGINATION_DEPTH) + +Rotação de chave: mudar ``SEARCH_CURSOR_HMAC_SECRET`` em settings → todos os +cursores antigos viram inválidos. Trade-off documentado em ADR-021. +""" +from __future__ import annotations + +import base64 +import binascii +import hashlib +import hmac +import json +import uuid +from datetime import datetime + +from django.conf import settings + +from .dto import CursorPayload + + +class InvalidCursorError(ValueError): + """Cursor inválido — assinatura, formato, depth ou payload corrompido. + + O view captura e devolve 400 com ``error='cursor_invalid'`` (Inv #5). + """ + + +def _b64encode(data: bytes) -> str: + """Base64 URL-safe sem padding (curto, URL-friendly).""" + return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii') + + +def _b64decode(data: str) -> bytes: + """Decode URL-safe sem padding (adiciona padding antes de decodar).""" + # base64.urlsafe_b64decode exige len % 4 == 0; padding com '=' + padded = data + ('=' * (-len(data) % 4)) + return base64.urlsafe_b64decode(padded.encode('ascii')) + + +def _sign(payload_b64: str) -> str: + secret = settings.SEARCH_CURSOR_HMAC_SECRET.encode('utf-8') + mac = hmac.new(secret, payload_b64.encode('ascii'), hashlib.sha256).digest() + return _b64encode(mac) + + +def encode_cursor(payload: CursorPayload) -> str: + """Serializa :class:`CursorPayload` em string ``.``. + + Score é ROUND(6) (Inv #6 simétrico com a CTE). Datetime em ISO8601. + UUID em str. Depth em int. + """ + body = { + 's': round(payload.score, 6), + 'p': payload.published_at.isoformat(), + 'i': str(payload.article_id), + 'd': payload.depth, + } + # json.dumps com sort_keys → cursor determinístico. + payload_json = json.dumps(body, sort_keys=True, separators=(',', ':')) + payload_b64 = _b64encode(payload_json.encode('utf-8')) + sig = _sign(payload_b64) + return f'{payload_b64}.{sig}' + + +def decode_cursor(cursor: str) -> CursorPayload: + """Verifica HMAC, valida depth, retorna :class:`CursorPayload`. + + Raises: + InvalidCursorError: qualquer falha (vazio, formato, base64 inválido, + HMAC mismatch, depth > cap, JSON malformado). View traduz para + 400 ``cursor_invalid``. + """ + if not cursor or '.' not in cursor: + raise InvalidCursorError('cursor vazio ou sem separador') + try: + payload_b64, sig_b64 = cursor.split('.', 1) + except ValueError as exc: + raise InvalidCursorError('formato inválido') from exc + + # HMAC check (timing-safe — L-04 SECURITY-REVIEW) + expected_sig = _sign(payload_b64) + if not hmac.compare_digest(expected_sig, sig_b64): + raise InvalidCursorError('assinatura HMAC inválida') + + # Decode + parse JSON + try: + raw = _b64decode(payload_b64) + body = json.loads(raw.decode('utf-8')) + except (binascii.Error, UnicodeDecodeError, json.JSONDecodeError) as exc: + raise InvalidCursorError('payload corrompido') from exc + + # Validação de schema mínima + try: + score = float(body['s']) + published_at = datetime.fromisoformat(body['p']) + article_id = uuid.UUID(body['i']) + depth = int(body['d']) + except (KeyError, TypeError, ValueError) as exc: + raise InvalidCursorError(f'schema inválido: {exc}') from exc + + # Inv #9 — cap de paginação profunda + max_depth = settings.SEARCH_MAX_PAGINATION_DEPTH + if depth < 0 or depth > max_depth: + raise InvalidCursorError( + f'depth {depth} fora do range [0..{max_depth}] — refine a busca' + ) + + return CursorPayload( + score=score, + published_at=published_at, + article_id=article_id, + depth=depth, + ) diff --git a/backend/apps/search/dto.py b/backend/apps/search/dto.py new file mode 100644 index 00000000..a48e67b5 --- /dev/null +++ b/backend/apps/search/dto.py @@ -0,0 +1,126 @@ +"""DTOs (frozen dataclasses) da busca editorial. + +Imutabilidade aqui reforça a **invariante #1 do algorithms specialist** +(determinismo): se o caller não pode mutar a spec depois de construída, +o teste property-based "mesma input → mesma ordem" se sustenta. + +DTOs canônicos: + + - :class:`QuerySpec` — input do ``SearchService.query()`` + - :class:`CursorPayload` — tupla decodificada do cursor HMAC + - :class:`ResultItem` — 1 item da resposta (dict-shape friendly) + - :class:`SearchResultPage` — output do ``SearchService.query()`` + +Convenção: nenhum método de domínio aqui. DTOs são "data only". Lógica de +ranking, sorting, paginação fica em ``services.py``. Lógica de +encode/decode HMAC fica em ``cursors.py`` (módulo separado para isolar +secret access). +""" +from __future__ import annotations + +import uuid +from dataclasses import dataclass, field +from datetime import datetime +from typing import Any + + +# ── Query input ────────────────────────────────────────────────────────────── + + +@dataclass(frozen=True, slots=True) +class QuerySpec: + """Input imutável do :func:`SearchService.query`. + + Campos seguem 1:1 a query string do endpoint (ADR-023). + + Args: + q: texto bruto da busca (antes de :func:`normalize_search_text`). + Validação 2 ≤ len ≤ 200 fica no serializer. + author_id: filtro opcional por UUID do autor. + category_id: filtro opcional por ID de categoria (BIGINT). + de: lower bound de ``published_at``. + ate: upper bound de ``published_at``. + cursor: cursor HMAC opaco (base64). ``None`` = primeira página. + per_page: tamanho da página (default 20, max 50 — validado no + serializer). + """ + + q: str + author_id: uuid.UUID | None = None + category_id: int | None = None + de: datetime | None = None + ate: datetime | None = None + cursor: str | None = None + per_page: int = 20 + + +# ── Cursor payload ─────────────────────────────────────────────────────────── + + +@dataclass(frozen=True, slots=True) +class CursorPayload: + """Tupla decodificada do cursor HMAC. + + Carrega o ESTADO MÍNIMO necessário para tuple comparison estável na CTE + `scored` (algorithms §7): + + (score, published_at, article_id) < (cursor_score, cursor_pub, cursor_id) + + Inv #6 — ``score`` é o valor já com ``ROUND(6)`` aplicado, simétrico + com o que sai do SELECT. Float drift na 15ª casa quebrava paginação. + + Inv #9 — ``depth`` é o nº de páginas já navegadas. Server rejeita >50 + com 400 ``refine_query`` (defesa A3 anti-paginação profunda). + """ + + score: float + published_at: datetime + article_id: uuid.UUID + depth: int + + +# ── Result item / page ─────────────────────────────────────────────────────── + + +@dataclass(frozen=True, slots=True) +class ResultItem: + """1 artigo na resposta da busca. + + Campos seguem o response shape do ADR-023 §"Endpoint contract". + + ``author`` e ``category`` são dicts pequenos (id + display + slug), + NÃO entidades ORM — anti N+1 já resolvido no service via + ``select_related`` + side-fetch ``in_bulk``. + """ + + article_id: uuid.UUID + title: str + slug: str + excerpt: str + published_at: datetime + author: dict[str, Any] + category: dict[str, Any] | None + cover_url: str | None + score: float + + +@dataclass(frozen=True, slots=True) +class SearchResultPage: + """Output completo do :func:`SearchService.query`. + + Args: + results: tupla de :class:`ResultItem` na ordem do ranking. + next_cursor: cursor HMAC base64 da próxima página, ou ``None`` se + esgotou (``hasMore`` deriva de ``next_cursor is not None``). + total_estimate: estimativa via EXPLAIN ROWS (ADR-025). + query_terms_expanded: stems pt-BR via ``ts_lexize`` (Inv #11), para + highlighting client-side correto. + took_ms: latência do service (DB + CTE + encode), excluindo + serialização DRF. + """ + + results: tuple[ResultItem, ...] = field(default_factory=tuple) + next_cursor: str | None = None + total_estimate: int = 0 + query_terms_expanded: tuple[str, ...] = field(default_factory=tuple) + took_ms: int = 0 diff --git a/backend/apps/search/management/__init__.py b/backend/apps/search/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/search/management/commands/__init__.py b/backend/apps/search/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/search/migrations/0001_initial.py b/backend/apps/search/migrations/0001_initial.py new file mode 100644 index 00000000..ffb0af2a --- /dev/null +++ b/backend/apps/search/migrations/0001_initial.py @@ -0,0 +1,339 @@ +"""Migration 0001 — Schema inicial da busca editorial. + +Cria, em Postgres: + + 1. Extension ``unaccent`` (exige SUPERUSER em provisão — TX-13). + 2. Função ``immutable_unaccent(regdictionary, text)`` declarada IMMUTABLE + PARALLEL SAFE (workaround para que ``unaccent`` possa ser usada em + índice expressão). + 3. ``CONFIGURATION pt_unaccent`` clonada de ``portuguese`` com + ``ALTER MAPPING ... WITH unaccent, portuguese_stem`` — pipeline FTS + canônico pt-BR (ADR-019). + 4. Função ``articles_search_config(text) RETURNS tsvector IMMUTABLE + PARALLEL SAFE`` — usada por trigger e queries (ADR-019). + 5. Tabela ``search_index`` (read-projection de Article) com + ``article_id UUID`` PK, ``search_vector tsvector``, weighted text + fields, ``author_id UUID``, ``category_id BIGINT NULL``, + ``published_at TIMESTAMPTZ`` e ``indexed_at TIMESTAMPTZ`` (ADR-016). + 6. Tabela ``search_log`` (analytics, retenção LGPD 7d). + +Em SQLite-dev (ADR-020), apenas o esqueleto mínimo das duas tabelas é criado +(sem extensions, configuration, função IMMUTABLE ou tsvector nativo). Tests +gated por ``@pytest.mark.requires_postgres`` cobrem o caminho Postgres real. + +Bugs corrigidos vs DESIGN v2 (specialist DB): + Bug 1: ``author_id UUID`` (User.id é UUID, não BIGINT). + Bug 2: configuration ``pt_unaccent`` preserva ``IMMUTABLE`` na função + ``articles_search_config`` (necessário para índice expressão). + +Refs: DESIGN.md §2.2; ADR-018; ADR-019; ADR-020; + _specialist-outputs/01-database-architect.md §1. +""" +from __future__ import annotations + +from django.db import migrations, models + + +# ── SQL Postgres-only ─────────────────────────────────────────────────────────── + +CREATE_EXTENSION_AND_FUNCTION_SQL = r""" +-- 1. Extension unaccent. Exige SUPERUSER na primeira execução em produção. +-- Em ambientes gerenciados, executar manualmente como admin do Postgres +-- antes de rodar migrate como usuário da app. +CREATE EXTENSION IF NOT EXISTS unaccent; + +-- 2. Wrapper IMMUTABLE de unaccent. +-- ``unaccent(regdictionary, text)`` em catalog é STABLE (depende de +-- ``unaccent.rules``). Postgres recusa criar índice expressão sobre função +-- STABLE → wrapper IMMUTABLE PARALLEL SAFE é o workaround padrão da +-- comunidade pt-BR (Bug 2 do specialist DB). +CREATE OR REPLACE FUNCTION public.immutable_unaccent(regdictionary, text) +RETURNS text AS $$ + SELECT unaccent($1, $2) +$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; + +-- 3. CONFIGURATION dedicada pt_unaccent — clona portuguese e troca o mapping +-- de palavras (hword, hword_part, word) para passar por unaccent ANTES +-- do portuguese_stem. Resultado: normalização token-a-token dentro do +-- pipeline FTS (preserva tratamento de stopwords pt-BR — ADR-019). +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_ts_config + WHERE cfgname = 'pt_unaccent' AND cfgnamespace = 'public'::regnamespace + ) THEN + CREATE TEXT SEARCH CONFIGURATION public.pt_unaccent ( + COPY = pg_catalog.portuguese + ); + END IF; +END $$; + +ALTER TEXT SEARCH CONFIGURATION public.pt_unaccent + ALTER MAPPING FOR hword, hword_part, word + WITH unaccent, portuguese_stem; + +-- 4. Função wrapper IMMUTABLE PARALLEL SAFE para uso em trigger e índice. +-- Esta é a função que a trigger ``trg_articles_sync_search`` (migration +-- 0003) chama com setweight A/B/C sobre title/excerpt/body. +CREATE OR REPLACE FUNCTION public.articles_search_config(text) +RETURNS tsvector AS $$ + SELECT to_tsvector('public.pt_unaccent'::regconfig, $1) +$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; +""" + +DROP_EXTENSION_AND_FUNCTION_SQL = r""" +DROP FUNCTION IF EXISTS public.articles_search_config(text); +DROP TEXT SEARCH CONFIGURATION IF EXISTS public.pt_unaccent; +DROP FUNCTION IF EXISTS public.immutable_unaccent(regdictionary, text); +-- NOTA: NÃO removemos a extension unaccent no rollback — outros apps podem +-- depender dela. Provisão é externa (TX-13). +""" + +CREATE_TABLES_POSTGRES_SQL = r""" +-- Tabela search_index — read-projection de Article (ADR-016). +-- Schema controlado por esta migration, NÃO pelo ORM (Meta.managed = False). +-- +-- Por que esses campos? Ver DESIGN.md §2.2: +-- * article_id UUID PK 1:1 com articles.id +-- * search_vector tsvector — composto por setweight A/B/C +-- * title_text / excerpt_text / body_text — cópias para ts_headline futuro +-- * author_id UUID — cópia para filtro sem JOIN +-- * category_id BIGINT NULL — cópia para filtro sem JOIN +-- * published_at TIMESTAMPTZ — recency decay +-- * indexed_at TIMESTAMPTZ — diagnóstico do último upsert +CREATE TABLE IF NOT EXISTS search_index ( + article_id UUID PRIMARY KEY + REFERENCES articles(id) ON DELETE CASCADE, + search_vector tsvector NOT NULL, + title_text TEXT NOT NULL, + excerpt_text TEXT NOT NULL DEFAULT '', + body_text TEXT NOT NULL, + -- Bug 1 do specialist DB: User.id é UUID, NÃO BIGINT. + -- FK aponta para users(id) (tabela do AbstractBaseUser custom em apps.users). + author_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + -- category_id continua BIGINT (Category usa BigAutoField padrão); NULL-able + -- porque Article.category é opcional (on_delete=SET_NULL). + category_id BIGINT REFERENCES categories(id) ON DELETE SET NULL, + published_at TIMESTAMPTZ NOT NULL, + indexed_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +COMMENT ON TABLE search_index IS + 'Read-projection de Article para FTS (ADR-016). Mantida pela trigger ' + 'trg_articles_sync_search (ADR-018). Código Python NUNCA escreve aqui.'; + +-- Tabela search_log — analytics, retenção LGPD 7d (RNF do DESIGN). +CREATE TABLE IF NOT EXISTS search_log ( + id BIGSERIAL PRIMARY KEY, + query_text TEXT NOT NULL DEFAULT '', + query_norm TEXT NOT NULL DEFAULT '', + filters_json JSONB NOT NULL DEFAULT '{}'::jsonb, + results_count INTEGER NOT NULL DEFAULT 0, + total_estimate INTEGER NOT NULL DEFAULT 0, + duration_ms INTEGER NOT NULL DEFAULT 0, + cache_hit BOOLEAN NOT NULL DEFAULT FALSE, + -- user_id é UUID opcional (anon = NULL). Sem PII além do ID interno. + user_id UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +COMMENT ON TABLE search_log IS + 'Log de buscas com retenção 7d (LGPD). Purga via Celery Beat (futuro).'; + +CREATE INDEX IF NOT EXISTS idx_search_log_created_at + ON search_log (created_at DESC); +""" + +DROP_TABLES_POSTGRES_SQL = r""" +DROP TABLE IF EXISTS search_log; +DROP TABLE IF EXISTS search_index; +""" + +# ── SQL SQLite-dev fallback (ADR-020) ─────────────────────────────────────────── +# SQLite NÃO suporta tsvector, CREATE EXTENSION, CREATE TEXT SEARCH CONFIGURATION, +# nem trigger PL/pgSQL. Criamos apenas o esqueleto das tabelas — search_vector +# vira TEXT NULL, sem trigger, sem GIN, sem foreign keys com ON DELETE CASCADE +# (que SQLite suporta mas com semântica restrita). Isso permite que o ORM mapeie +# os models com managed=False sem que a migration trave. Tests reais de FTS são +# gated por @pytest.mark.requires_postgres. + +CREATE_TABLES_SQLITE_SQL = r""" +CREATE TABLE IF NOT EXISTS search_index ( + article_id CHAR(32) PRIMARY KEY, + search_vector TEXT, + title_text TEXT NOT NULL, + excerpt_text TEXT NOT NULL DEFAULT '', + body_text TEXT NOT NULL, + author_id CHAR(32) NOT NULL, + category_id BIGINT, + published_at DATETIME NOT NULL, + indexed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS search_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + query_text TEXT NOT NULL DEFAULT '', + query_norm TEXT NOT NULL DEFAULT '', + filters_json TEXT NOT NULL DEFAULT '{}', + results_count INTEGER NOT NULL DEFAULT 0, + total_estimate INTEGER NOT NULL DEFAULT 0, + duration_ms INTEGER NOT NULL DEFAULT 0, + cache_hit INTEGER NOT NULL DEFAULT 0, + user_id CHAR(32), + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_search_log_created_at + ON search_log (created_at DESC); +""" + +DROP_TABLES_SQLITE_SQL = r""" +DROP TABLE IF EXISTS search_log; +DROP TABLE IF EXISTS search_index; +""" + + +# ── Operações condicionais por vendor (ADR-020) ──────────────────────────────── + + +def _split_sql_statements(sql: str) -> list[str]: + """Quebra SQL multi-statement em statements individuais. + + Necessário porque o driver SQLite (sqlite3) só aceita 1 statement por + ``execute()``. Postgres aceita multi-statement, mas split é seguro para + ambos. + + Não é parser SQL completo; assume: + - Statements separados por ';' no fim da linha. + - Blocos DO $$ ... $$ usam ``$$`` como delimitador e NÃO devem ser + quebrados pelo ';' interno. + """ + statements: list[str] = [] + buffer: list[str] = [] + in_dollar_block = False + for line in sql.splitlines(): + stripped = line.strip() + if not stripped or stripped.startswith('--'): + buffer.append(line) + continue + # Tracking de blocos DO $$ ... $$ (sequência ímpar = entra/sai) + dollar_count = stripped.count('$$') + if dollar_count: + in_dollar_block = (in_dollar_block + dollar_count) % 2 == 1 + buffer.append(line) + if not in_dollar_block and stripped.endswith(';'): + stmt = '\n'.join(buffer).strip() + if stmt: + statements.append(stmt) + buffer = [] + tail = '\n'.join(buffer).strip() + if tail: + statements.append(tail) + return statements + + +def _execute_many(schema_editor, sql: str) -> None: + """Executa SQL multi-statement em chunks individuais (compat SQLite).""" + for stmt in _split_sql_statements(sql): + schema_editor.execute(stmt) + + +def _apply_postgres_only(apps, schema_editor): + """Roda as DDLs Postgres-only (extensions, configuration, função IMMUTABLE, + tabelas full com tsvector + FKs). + + Em SQLite, roda o fallback mínimo (sem extensions, sem tsvector real). + """ + vendor = schema_editor.connection.vendor + if vendor == 'postgresql': + _execute_many(schema_editor, CREATE_EXTENSION_AND_FUNCTION_SQL) + _execute_many(schema_editor, CREATE_TABLES_POSTGRES_SQL) + else: + # SQLite-dev (ADR-020). Outros vendors caem aqui também — assumimos + # comportamento "best effort" via SQL ANSI mínimo. Em CI sempre Postgres. + _execute_many(schema_editor, CREATE_TABLES_SQLITE_SQL) + + +def _reverse_postgres_only(apps, schema_editor): + """Rollback simétrico ao :func:`_apply_postgres_only`.""" + vendor = schema_editor.connection.vendor + if vendor == 'postgresql': + _execute_many(schema_editor, DROP_TABLES_POSTGRES_SQL) + _execute_many(schema_editor, DROP_EXTENSION_AND_FUNCTION_SQL) + else: + _execute_many(schema_editor, DROP_TABLES_SQLITE_SQL) + + +class Migration(migrations.Migration): + """Schema inicial da busca editorial. + + ``atomic = True`` é OK aqui: CREATE EXTENSION / CONFIGURATION / FUNCTION / + TABLE rodam dentro de transação em Postgres. CONCURRENTLY (que exige + ``atomic = False``) só aparece na migration ``0002_search_indexes``. + """ + + initial = True + + dependencies = [ + # Aguarda tabela articles (FK article_id → articles.id) e users + # (FK author_id → users.id) e categories (FK category_id → categories.id). + # Targeted nas últimas migrations de cada app para garantir que o schema + # final esteja estabilizado antes de adicionar FKs. + ('articles', '0004_article_cover_caption'), + ('users', '0004_user_role_editor_label'), + ] + + operations = [ + # 1. Registra os models no state do Django (managed=False → ORM NÃO + # cria/altera schema; ORM apenas mapeia para queries). + migrations.CreateModel( + name='SearchIndex', + fields=[ + ('article_id', models.UUIDField(primary_key=True, serialize=False, db_column='article_id')), + ('search_vector', models.TextField(blank=True, null=True, db_column='search_vector')), + ('title_text', models.TextField(db_column='title_text')), + ('excerpt_text', models.TextField(blank=True, default='', db_column='excerpt_text')), + ('body_text', models.TextField(db_column='body_text')), + ('author_id', models.UUIDField(db_column='author_id')), + ('category_id', models.BigIntegerField(blank=True, null=True, db_column='category_id')), + ('published_at', models.DateTimeField(db_column='published_at')), + ('indexed_at', models.DateTimeField(db_column='indexed_at')), + ], + options={ + 'managed': False, + 'db_table': 'search_index', + 'verbose_name': 'Entrada do índice de busca', + 'verbose_name_plural': 'Entradas do índice de busca', + }, + ), + migrations.CreateModel( + name='SearchLog', + fields=[ + ('id', models.BigAutoField(primary_key=True, serialize=False)), + ('query_text', models.TextField(blank=True, default='')), + ('query_norm', models.TextField(blank=True, default='')), + ('filters_json', models.JSONField(blank=True, default=dict)), + ('results_count', models.IntegerField(default=0)), + ('total_estimate', models.IntegerField(default=0)), + ('duration_ms', models.IntegerField(default=0)), + ('cache_hit', models.BooleanField(default=False)), + ('user_id', models.UUIDField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), + ], + options={ + 'managed': False, + 'db_table': 'search_log', + 'verbose_name': 'Log de busca', + 'verbose_name_plural': 'Logs de busca', + 'ordering': ['-created_at'], + }, + ), + # 2. Cria o schema real via SQL (extensions, configuration, função + # IMMUTABLE, tabelas com tsvector, FKs com ON DELETE CASCADE). + # Em SQLite, fallback mínimo (sem FTS real). + migrations.RunPython( + code=_apply_postgres_only, + reverse_code=_reverse_postgres_only, + elidable=False, + ), + ] diff --git a/backend/apps/search/migrations/0002_search_indexes.py b/backend/apps/search/migrations/0002_search_indexes.py new file mode 100644 index 00000000..f8f861a6 --- /dev/null +++ b/backend/apps/search/migrations/0002_search_indexes.py @@ -0,0 +1,111 @@ +"""Migration 0002 — Índices da busca editorial. + +Cria 4 índices em ``search_index``: + + 1. ``idx_search_vector_gin`` — GIN sobre ``search_vector`` (FTS). + 2. ``idx_search_category_published`` — composite parcial + ``(category_id, published_at DESC) WHERE category_id IS NOT NULL``. + 3. ``idx_search_author_pub_covering`` — composite com + ``INCLUDE (article_id)`` para index-only scan no caminho filter-por-autor. + 4. ``idx_search_published_only`` — BTree em ``published_at DESC`` para + ordenação por recência sem texto. + +Por que ``atomic = False``: + ``CREATE INDEX CONCURRENTLY`` em Postgres NÃO pode rodar dentro de + transação. Django wrap migrations em transação por default; aqui + desligamos com ``atomic = False`` para que CONCURRENTLY seja aceito + (ADR-030-DB §"`CREATE INDEX CONCURRENTLY` exige `atomic = False`"). + +Em SQLite-dev (ADR-020): pulado completamente. SQLite não tem CONCURRENTLY, +não tem GIN, não suporta partial indexes com expressões pt-BR. Em local-dev +o ``__icontains`` fallback do SearchService (Fase 2) é suficiente. + +Refs: DESIGN.md §2.2 (Indexes refinados); + ADR-030-DB (composite parciais + covering); + _specialist-outputs/01-database-architect.md §1 (Bug 5). +""" +from __future__ import annotations + +from django.db import migrations + + +# 4 statements separados — CONCURRENTLY não pode rodar dentro de tx, e o +# driver psycopg também aceita 1 stmt por execute() quando usamos schema_editor. +CREATE_INDEXES_SQL = [ + # 1. GIN sobre search_vector — coração da busca FTS. + """ + CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_search_vector_gin + ON search_index USING GIN (search_vector); + """, + # 2. Composite parcial por editoria (ADR-030-DB §"Por que parcial em + # category_id"). WHERE NOT NULL economiza ~40% de tamanho e elimina + # write amplification quando category_id IS NULL no upsert. + """ + CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_search_category_published + ON search_index (category_id, published_at DESC) + WHERE category_id IS NOT NULL; + """, + # 3. Composite com covering INCLUDE (ADR-030-DB §"Por que covering em + # author_id"). Permite index-only scan na CTE candidate-narrowing + # quando o filtro é só por autor (Postgres devolve article_id sem heap + # fetch). + """ + CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_search_author_pub_covering + ON search_index (author_id, published_at DESC) + INCLUDE (article_id); + """, + # 4. BTree em published_at DESC para ordenação por recência sem texto. + """ + CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_search_published_only + ON search_index (published_at DESC); + """, +] + +DROP_INDEXES_SQL = [ + 'DROP INDEX CONCURRENTLY IF EXISTS idx_search_published_only;', + 'DROP INDEX CONCURRENTLY IF EXISTS idx_search_author_pub_covering;', + 'DROP INDEX CONCURRENTLY IF EXISTS idx_search_category_published;', + 'DROP INDEX CONCURRENTLY IF EXISTS idx_search_vector_gin;', +] + + +def _create_indexes(apps, schema_editor) -> None: + """Cria os 4 índices em Postgres. No-op em SQLite-dev.""" + if schema_editor.connection.vendor != 'postgresql': + return # SQLite: SearchService usa __icontains fallback (ADR-020). + for stmt in CREATE_INDEXES_SQL: + schema_editor.execute(stmt.strip()) + + +def _drop_indexes(apps, schema_editor) -> None: + """Rollback simétrico ao :func:`_create_indexes`.""" + if schema_editor.connection.vendor != 'postgresql': + return + for stmt in DROP_INDEXES_SQL: + schema_editor.execute(stmt.strip()) + + +class Migration(migrations.Migration): + """Índices da busca editorial. + + ``atomic = False`` é REQUISITO DURO (não retirar): ``CREATE INDEX + CONCURRENTLY`` é rejeitado pelo Postgres dentro de transação. + Consequência: se UMA das 4 instruções falhar, as anteriores ficam + aplicadas (Postgres permite resume manual). Em CI isso é raro porque + a tabela está vazia; em produção, gerentes de migration devem confirmar + estado dos índices via ``\\d+ search_index`` em caso de falha parcial. + """ + + atomic = False + + dependencies = [ + ('search', '0001_initial'), + ] + + operations = [ + migrations.RunPython( + code=_create_indexes, + reverse_code=_drop_indexes, + elidable=False, + ), + ] diff --git a/backend/apps/search/migrations/0003_search_triggers.py b/backend/apps/search/migrations/0003_search_triggers.py new file mode 100644 index 00000000..6b786804 --- /dev/null +++ b/backend/apps/search/migrations/0003_search_triggers.py @@ -0,0 +1,197 @@ +"""Migration 0003 — Triggers SQL de sincronia Article → SearchIndex (ADR-018). + +Cria, em Postgres: + + 1. Função PL/pgSQL ``trg_articles_sync_search()`` — UPSERT idempotente + em ``search_index`` quando ``Article.status = 'published' AND + published_at IS NOT NULL``; DELETE quando rebaixado para draft. + 2. Função PL/pgSQL ``trg_articles_remove_search()`` — DELETE em + ``search_index`` em DELETE de Article. + 3. Trigger ``articles_sync_search`` AFTER INSERT OR UPDATE OF + (status, published_at, title, excerpt, body, author_id, category_id) + ON articles — escopo de campos relevantes apenas, reduz overhead. + 4. Trigger ``articles_remove_search`` AFTER DELETE ON articles. + +Trigger SQL é a **fonte de verdade da consistência** (ADR-018). Garante +sincronia sob 4 cenários onde signal Python falha: + - bulk_update / QuerySet.update() (Django NÃO dispara signal) + - Raw SQL (UPDATE articles SET status=...) + - Fixture loaddata em CI/dev + - Restore parcial pós-incidente (pg_restore --table=articles) + +Em SQLite-dev (ADR-020): no-op. SQLite suporta trigger SQL limitada mas não +PL/pgSQL, tsvector ou setweight. SearchService Fase 2 usa __icontains. + +Refs: DESIGN.md §2.2 (Decisão refinada — Trigger SQL + Signal); + ADR-018 (trigger = fonte de verdade); + ADR-019 (articles_search_config IMMUTABLE usada na trigger); + _specialist-outputs/01-database-architect.md §1 (Bug 3, Bug 4). +""" +from __future__ import annotations + +from django.db import migrations + + +# Função de UPSERT (publicação + reindex de campo) / DELETE (despublicação). +# Tratamentos: +# * NEW.status = 'published' AND NEW.published_at IS NOT NULL → UPSERT +# com setweight A/B/C de title/excerpt/body via articles_search_config +# (configuration pt_unaccent — ADR-019). +# * Caso contrário (draft, ou published_at NULL) → DELETE da projeção. +# Esse branch cobre o "fantasma do publicado" (Bug 3 do specialist DB). +CREATE_SYNC_FUNCTION_SQL = r""" +CREATE OR REPLACE FUNCTION public.trg_articles_sync_search() +RETURNS trigger AS $$ +BEGIN + IF NEW.status = 'published' AND NEW.published_at IS NOT NULL THEN + INSERT INTO search_index ( + article_id, search_vector, + title_text, excerpt_text, body_text, + author_id, category_id, published_at, indexed_at + ) VALUES ( + NEW.id, + setweight(public.articles_search_config(COALESCE(NEW.title, '')), 'A') || + setweight(public.articles_search_config(COALESCE(NEW.excerpt, '')), 'B') || + setweight(public.articles_search_config(COALESCE(NEW.body, '')), 'C'), + COALESCE(NEW.title, ''), + COALESCE(NEW.excerpt, ''), + COALESCE(NEW.body, ''), + NEW.author_id, + NEW.category_id, + NEW.published_at, + NOW() + ) + ON CONFLICT (article_id) DO UPDATE SET + search_vector = EXCLUDED.search_vector, + title_text = EXCLUDED.title_text, + excerpt_text = EXCLUDED.excerpt_text, + body_text = EXCLUDED.body_text, + author_id = EXCLUDED.author_id, + category_id = EXCLUDED.category_id, + published_at = EXCLUDED.published_at, + indexed_at = NOW(); + ELSE + -- Despublicação (status virou draft, OR published_at virou NULL): + -- remove projeção. Corrige Bug 3 do specialist DB. + DELETE FROM search_index WHERE article_id = NEW.id; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +""" + +# Função de remoção em DELETE de Article (CASCADE da FK também removeria, mas +# manter trigger explícita facilita debug e cobre cenário onde FK foi +# desabilitada temporariamente — ex.: session_replication_role = 'replica'). +CREATE_REMOVE_FUNCTION_SQL = r""" +CREATE OR REPLACE FUNCTION public.trg_articles_remove_search() +RETURNS trigger AS $$ +BEGIN + DELETE FROM search_index WHERE article_id = OLD.id; + RETURN OLD; +END; +$$ LANGUAGE plpgsql; +""" + +# Trigger AFTER INSERT OR UPDATE OF — escopo limitado a campos relevantes. +# UPDATE em view_count, is_featured, slug etc. NÃO dispara reindex (reduz custo). +# IF NOT EXISTS não está disponível em CREATE TRIGGER (Postgres < 14 não suporta, +# 14+ sim); usamos DROP IF EXISTS + CREATE para idempotência cross-version. +CREATE_TRIGGERS_SQL = r""" +DROP TRIGGER IF EXISTS articles_sync_search ON articles; +CREATE TRIGGER articles_sync_search + AFTER INSERT OR UPDATE OF + status, published_at, title, excerpt, body, author_id, category_id + ON articles + FOR EACH ROW + EXECUTE FUNCTION public.trg_articles_sync_search(); + +DROP TRIGGER IF EXISTS articles_remove_search ON articles; +CREATE TRIGGER articles_remove_search + AFTER DELETE ON articles + FOR EACH ROW + EXECUTE FUNCTION public.trg_articles_remove_search(); +""" + +# Rollback simétrico. +DROP_TRIGGERS_SQL = r""" +DROP TRIGGER IF EXISTS articles_remove_search ON articles; +DROP TRIGGER IF EXISTS articles_sync_search ON articles; +""" + +DROP_FUNCTIONS_SQL = r""" +DROP FUNCTION IF EXISTS public.trg_articles_remove_search(); +DROP FUNCTION IF EXISTS public.trg_articles_sync_search(); +""" + + +def _split_statements(sql: str) -> list[str]: + """Quebra SQL multi-statement respeitando blocos PL/pgSQL ``$$ ... $$``. + + Mesmo splitter da migration 0001 (não compartilhado para evitar import + cíclico entre migrations). + """ + statements: list[str] = [] + buffer: list[str] = [] + in_dollar_block = False + for line in sql.splitlines(): + stripped = line.strip() + if not stripped or stripped.startswith('--'): + buffer.append(line) + continue + dollar_count = stripped.count('$$') + if dollar_count: + in_dollar_block = (in_dollar_block + dollar_count) % 2 == 1 + buffer.append(line) + if not in_dollar_block and stripped.endswith(';'): + stmt = '\n'.join(buffer).strip() + if stmt: + statements.append(stmt) + buffer = [] + tail = '\n'.join(buffer).strip() + if tail: + statements.append(tail) + return statements + + +def _apply_triggers(apps, schema_editor) -> None: + """Cria função + 2 triggers em Postgres. No-op em SQLite-dev.""" + if schema_editor.connection.vendor != 'postgresql': + return + for sql in ( + CREATE_SYNC_FUNCTION_SQL, + CREATE_REMOVE_FUNCTION_SQL, + CREATE_TRIGGERS_SQL, + ): + for stmt in _split_statements(sql): + schema_editor.execute(stmt) + + +def _drop_triggers(apps, schema_editor) -> None: + """Rollback simétrico ao :func:`_apply_triggers`.""" + if schema_editor.connection.vendor != 'postgresql': + return + for sql in (DROP_TRIGGERS_SQL, DROP_FUNCTIONS_SQL): + for stmt in _split_statements(sql): + schema_editor.execute(stmt) + + +class Migration(migrations.Migration): + """Triggers SQL de sincronia Article → SearchIndex. + + ``atomic = True`` é OK: CREATE FUNCTION e CREATE TRIGGER aceitam + transação. Importante rodar DEPOIS dos índices (0002) para que o GIN + exista antes do primeiro insert massivo via backfill (Fase 2 reindex). + """ + + dependencies = [ + ('search', '0002_search_indexes'), + ] + + operations = [ + migrations.RunPython( + code=_apply_triggers, + reverse_code=_drop_triggers, + elidable=False, + ), + ] diff --git a/backend/apps/search/migrations/0004_search_vacuum_tuning.py b/backend/apps/search/migrations/0004_search_vacuum_tuning.py new file mode 100644 index 00000000..078e0ae7 --- /dev/null +++ b/backend/apps/search/migrations/0004_search_vacuum_tuning.py @@ -0,0 +1,104 @@ +"""Migration 0004 — Vacuum tuning agressivo para GIN + search_index (ADR-034). + +Configura, em Postgres: + + * Storage params do índice GIN ``idx_search_vector_gin``: + - ``fastupdate = on``: insertions vão para pending list (buffer) antes + de virem ao índice principal. Reduz custo write online. + - ``gin_pending_list_limit = '2MB'``: flush mais frequente (vs 4MB + default) → flushes pequenos e previsíveis em vez de spikes. + + * Storage params da tabela ``search_index``: + - ``autovacuum_vacuum_scale_factor = 0.05``: vacuum dispara quando 5% + das linhas modificadas (vs 20% default) → autovacuum 4× mais + frequente para search-heavy. + - ``autovacuum_analyze_scale_factor = 0.02``: analyze a cada 2% + modificado → statistics fresh para o planner (plan_rows do ADR-025). + - ``autovacuum_vacuum_cost_delay = '10ms'``: pacing throttle mais + agressivo (vs 20ms default). KVM 1 I/O tolera. + +Em SQLite-dev (ADR-020): no-op. SQLite não tem GIN nem autovacuum params +table-level. + +Refs: DESIGN.md §2.2 (Vacuum tuning); ADR-034; + _specialist-outputs/01-database-architect.md §2 Gap E. +""" +from __future__ import annotations + +from django.db import migrations + + +# Cada statement é um único comando — driver SQLite aceita 1 stmt/execute(). +TUNING_STATEMENTS = [ + # GIN fastupdate + pending list limit + """ + ALTER INDEX idx_search_vector_gin SET ( + fastupdate = on, + gin_pending_list_limit = 2048 + ); + """, + # Autovacuum agressivo na tabela search_index + """ + ALTER TABLE search_index SET ( + autovacuum_vacuum_scale_factor = 0.05, + autovacuum_analyze_scale_factor = 0.02, + autovacuum_vacuum_cost_delay = 10 + ); + """, +] + +# Reverse — volta aos defaults (RESET = remove o storage param customizado). +RESET_STATEMENTS = [ + """ + ALTER INDEX idx_search_vector_gin RESET ( + fastupdate, + gin_pending_list_limit + ); + """, + """ + ALTER TABLE search_index RESET ( + autovacuum_vacuum_scale_factor, + autovacuum_analyze_scale_factor, + autovacuum_vacuum_cost_delay + ); + """, +] + + +def _apply_tuning(apps, schema_editor) -> None: + """Aplica vacuum tuning em Postgres. No-op em SQLite-dev.""" + if schema_editor.connection.vendor != 'postgresql': + return + for stmt in TUNING_STATEMENTS: + schema_editor.execute(stmt.strip()) + + +def _reset_tuning(apps, schema_editor) -> None: + """Rollback simétrico: volta aos defaults Postgres.""" + if schema_editor.connection.vendor != 'postgresql': + return + for stmt in RESET_STATEMENTS: + schema_editor.execute(stmt.strip()) + + +class Migration(migrations.Migration): + """Vacuum tuning — última migration da Fase 1. + + NOTA sobre unidades: + - ``gin_pending_list_limit`` aceita inteiro em KB (PostgreSQL 13+). + ``'2MB'`` em string só funciona em SHOW; ALTER exige inteiro KB + (2048 = 2MB). + - ``autovacuum_vacuum_cost_delay`` em ms inteiro. + """ + + dependencies = [ + ('search', '0003_search_triggers'), + ] + + operations = [ + migrations.RunPython( + code=_apply_tuning, + reverse_code=_reset_tuning, + elidable=False, + ), + ] diff --git a/backend/apps/search/migrations/0005_trigger_enable_always.py b/backend/apps/search/migrations/0005_trigger_enable_always.py new file mode 100644 index 00000000..9a5a9b5d --- /dev/null +++ b/backend/apps/search/migrations/0005_trigger_enable_always.py @@ -0,0 +1,85 @@ +"""Migration 0005 — Triggers ENABLE ALWAYS (ADR-039 + REVIEW-PHASE-1 H-01). + +Fix do achado 🟠 High do code-review da Fase 1: as triggers criadas pela +migration 0003 estão em modo ``ENABLE`` (ORIGIN) default. Esse modo é +**bypassado** por:: + + SET session_replication_role = 'replica'; + +Vetor (CWE-863, Incorrect Authorization Check): + + 1. Atacante com role REPLICATION (raro) ou usuário ``postgres`` + (host comprometido) executa o SET acima. + 2. Triggers em modo ORIGIN não disparam para a sessão. + 3. INSERT/UPDATE em ``articles`` não sincroniza ``search_index``. + 4. Drift permanente: artigos publicados desaparecem da busca, ou + mantêm projeção stale (search_vector desatualizado). + +Defesa: marcar as triggers como ``ENABLE ALWAYS`` (``pg_trigger.tgenabled = +'A'``). Triggers ALWAYS disparam mesmo em modo replica. + +Rollback simétrico: ``ENABLE`` puro (volta ao default ORIGIN). Não usa +``DISABLE`` porque queremos preservar o comportamento da 0003. + +Em SQLite-dev (ADR-020): no-op. Trigger SQLite não tem ALWAYS/REPLICA +modes — o catálogo Postgres `pg_trigger` nem existe. + +Referências: + + - ADR-039 (Test integration trigger bypass session_replication_role) + - REVIEW-PHASE-1.md §3 H-01 + - SECURITY-REVIEW.md §3 M-04 +""" +from __future__ import annotations + +from django.db import migrations + + +ENABLE_ALWAYS_SQL = r""" +ALTER TABLE articles ENABLE ALWAYS TRIGGER articles_sync_search; +ALTER TABLE articles ENABLE ALWAYS TRIGGER articles_remove_search; +""" + +# Rollback: volta ao default ENABLE (ORIGIN). Não DISABLE — queremos que a +# sincronia continue funcionando em modo single-session normal. +ENABLE_ORIGIN_SQL = r""" +ALTER TABLE articles ENABLE TRIGGER articles_sync_search; +ALTER TABLE articles ENABLE TRIGGER articles_remove_search; +""" + + +def _apply_enable_always(apps, schema_editor) -> None: + """Marca as duas triggers como ``ENABLE ALWAYS``. No-op em SQLite-dev.""" + if schema_editor.connection.vendor != 'postgresql': + return + for stmt in (s.strip() for s in ENABLE_ALWAYS_SQL.split(';') if s.strip()): + schema_editor.execute(stmt) + + +def _reverse_enable_always(apps, schema_editor) -> None: + """Rollback: volta ao default ORIGIN (``ENABLE`` puro).""" + if schema_editor.connection.vendor != 'postgresql': + return + for stmt in (s.strip() for s in ENABLE_ORIGIN_SQL.split(';') if s.strip()): + schema_editor.execute(stmt) + + +class Migration(migrations.Migration): + """ENABLE ALWAYS para triggers de sincronia search_index. + + ``atomic = True`` (default): ALTER TABLE ... ENABLE TRIGGER é DDL leve + que aceita transação. Não usa CONCURRENTLY, não trava operações de + leitura — apenas atualiza catálogo. + """ + + dependencies = [ + ('search', '0004_search_vacuum_tuning'), + ] + + operations = [ + migrations.RunPython( + code=_apply_enable_always, + reverse_code=_reverse_enable_always, + elidable=False, + ), + ] diff --git a/backend/apps/search/migrations/__init__.py b/backend/apps/search/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/search/models.py b/backend/apps/search/models.py new file mode 100644 index 00000000..c4204c66 --- /dev/null +++ b/backend/apps/search/models.py @@ -0,0 +1,109 @@ +"""Models do app de busca editorial. + +Os models aqui são **managed = False** porque a tabela ``search_index`` é +criada e mantida via SQL puro nas migrations (extension ``unaccent``, +``CONFIGURATION pt_unaccent``, função ``articles_search_config`` e trigger SQL +``trg_articles_sync_search`` — ADR-018, ADR-019). O ORM Django apenas mapeia a +tabela para uso em queries, mas NÃO controla seu schema (ADR-018 deixa a +trigger como fonte de verdade da consistência). + +Por que ``managed = False``: + - Schema tem extensions, configurations e funções SQL que o ORM não + consegue expressar. + - O conjunto de índices é definido em ``RunSQL`` (CONCURRENTLY, partial, + INCLUDE) — também fora do alcance do ORM. + - Trigger é a fonte de verdade da sincronia; ORM não deve "achar" que é + dono do schema. + +Em ambientes SQLite-dev (DESIGN §3.6 / ADR-020), a tabela é criada via fallback +mínimo na migration 0001 (sem extension, sem trigger, sem GIN). O SearchService +faz fallback ``__icontains`` quando ``connection.vendor != 'postgresql'``. +""" +from __future__ import annotations + +from django.conf import settings +from django.db import models + + +class SearchIndex(models.Model): + """Read-projection de :class:`apps.articles.models.Article` otimizada para FTS. + + Esta tabela é populada exclusivamente pela trigger Postgres + ``trg_articles_sync_search`` (ADR-018). Código Python NUNCA escreve aqui + diretamente — qualquer write é violação do ADR. + + Campos: + article_id: FK 1:1 para ``articles.id`` (UUID). Chave primária da projeção. + search_vector: ``tsvector`` composto por ``setweight`` A/B/C (título, + excerpt, body) usando a configuration ``pt_unaccent`` (ADR-019). + title_text/excerpt_text/body_text: cópias do texto original (usados em + highlighting via ``ts_headline`` server-side, se decidido — DESIGN + §3.7). + author_id: cópia de ``Article.author_id`` (UUID) para filtros sem JOIN. + category_id: cópia de ``Article.category_id`` (BIGINT, nullable) para + filtros sem JOIN. + published_at: cópia para ordenação por recência (recency decay). + indexed_at: timestamp do último upsert pela trigger (debug/diagnóstico). + """ + + article_id = models.UUIDField(primary_key=True, db_column='article_id') + # search_vector é tsvector em Postgres. SQLite-dev mapeia para TEXT (NULL) + # apenas para que o ORM não exploda; queries reais ficam Postgres-only. + search_vector = models.TextField(db_column='search_vector', null=True, blank=True) + title_text = models.TextField(db_column='title_text') + excerpt_text = models.TextField(db_column='excerpt_text', blank=True, default='') + body_text = models.TextField(db_column='body_text') + author_id = models.UUIDField(db_column='author_id') + category_id = models.BigIntegerField(db_column='category_id', null=True, blank=True) + published_at = models.DateTimeField(db_column='published_at') + indexed_at = models.DateTimeField(db_column='indexed_at') + + class Meta: + managed = False # Schema controlado por migration SQL pura. + db_table = 'search_index' + verbose_name = 'Entrada do índice de busca' + verbose_name_plural = 'Entradas do índice de busca' + + def __str__(self) -> str: + return f'SearchIndex(article_id={self.article_id})' + + +class SearchLog(models.Model): + """Log estruturado de buscas para analytics e troubleshooting. + + Retenção 7 dias (LGPD — RNF e cyber-security). Purga via cron (Celery + Beat) ou TX externa. Cada linha registra uma busca executada, sem PII do + usuário (apenas ``user_id`` opcional, que é UUID interno). + """ + + id = models.BigAutoField(primary_key=True) + query_text = models.TextField(blank=True, default='') + query_norm = models.TextField( + blank=True, + default='', + help_text='Query normalizada (lower, strip, sem operadores tsquery).', + ) + filters_json = models.JSONField( + blank=True, + default=dict, + help_text='Snapshot dos filtros aplicados (autor, categoria, datas).', + ) + results_count = models.IntegerField(default=0) + total_estimate = models.IntegerField( + default=0, + help_text='Estimativa via EXPLAIN ROWS (algorithms §2.3).', + ) + duration_ms = models.IntegerField(default=0) + cache_hit = models.BooleanField(default=False) + user_id = models.UUIDField(null=True, blank=True) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + + class Meta: + managed = False # Criada em migration 0001 via RunSQL. + db_table = 'search_log' + verbose_name = 'Log de busca' + verbose_name_plural = 'Logs de busca' + ordering = ['-created_at'] + + def __str__(self) -> str: + return f'SearchLog({self.query_text!r}, {self.results_count} results)' diff --git a/backend/apps/search/serializers.py b/backend/apps/search/serializers.py new file mode 100644 index 00000000..e6f6b418 --- /dev/null +++ b/backend/apps/search/serializers.py @@ -0,0 +1,162 @@ +"""Serializers DRF do endpoint ``/api/v1/search/articles/``. + +Composição: + + - :class:`SearchQuerySerializer` valida QUERY STRING (q, filtros, + cursor, per_page) → :class:`apps.search.dto.QuerySpec`. + - :class:`SearchResultItemSerializer` projeta :class:`ResultItem` no JSON + contratual (ADR-023). + - :class:`SearchResultPageSerializer` envelopa o page final. + +Defesa H-01 (SECURITY-REVIEW): ``q`` é validado contra whitelist +alfanumérico + acento + espaço + hífen. Chars HTML levantam 400 +``invalid_chars``. ``normalize_search_text`` aplica camada redundante. +""" +from __future__ import annotations + +import re +import uuid +from datetime import datetime +from typing import Final + +from django.conf import settings +from rest_framework import serializers + +from .dto import QuerySpec + + +# Whitelist alinhada com `utils._ALLOWED_CHARS_RE` (camadas redundantes). +# Aqui o teste é ESTRITO (qualquer char fora → 400) para garantir input +# limpo já no serializer — defesa H-01 sem depender da normalização. +_Q_ALLOWED_RE: Final[re.Pattern[str]] = re.compile(r'^[a-zA-ZÀ-ſ0-9\s\-]+$') + + +class SearchQuerySerializer(serializers.Serializer): + """Serializa a query string em :class:`QuerySpec`. + + Validação aderente a ADR-023 + DESIGN §2.4: + + - ``q``: required, 2 ≤ len ≤ 200, whitelist chars (H-01 defesa) + - ``author``: UUID opcional + - ``category``: int opcional + - ``de``, ``ate``: ISO8601 opcionais + - ``cursor``: string opcional + - ``per_page``: int default 20, max 50 + """ + + q = serializers.CharField( + required=True, allow_blank=False, trim_whitespace=True, + ) + author = serializers.UUIDField(required=False, allow_null=True) + category = serializers.IntegerField(required=False, allow_null=True, min_value=1) + de = serializers.DateTimeField(required=False, allow_null=True) + ate = serializers.DateTimeField(required=False, allow_null=True) + cursor = serializers.CharField(required=False, allow_blank=True, allow_null=True) + per_page = serializers.IntegerField( + required=False, + min_value=1, + # Limit aplicado em validate_per_page para usar settings, não literal. + ) + + def validate_q(self, value: str) -> str: + """Whitelist H-01 + bounds 2..200 chars.""" + if len(value) < settings.SEARCH_MIN_Q_LENGTH: + raise serializers.ValidationError( + {'error': 'query_too_short', + 'detail': f'mínimo {settings.SEARCH_MIN_Q_LENGTH} caracteres'}, + code='query_too_short', + ) + if len(value) > settings.SEARCH_MAX_Q_LENGTH: + raise serializers.ValidationError( + {'error': 'query_too_long', + 'detail': f'máximo {settings.SEARCH_MAX_Q_LENGTH} caracteres'}, + code='query_too_long', + ) + if not _Q_ALLOWED_RE.match(value): + # Defesa H-01 — chars HTML (<, >, /, &, ") rejeitados. + raise serializers.ValidationError( + {'error': 'invalid_chars', + 'detail': 'caracteres não permitidos (apenas letras, ' + 'números, acentos, espaço e hífen)'}, + code='invalid_chars', + ) + return value + + def validate_per_page(self, value: int) -> int: + if value > settings.SEARCH_MAX_PER_PAGE: + raise serializers.ValidationError( + {'error': 'per_page_too_large', + 'detail': f'máximo {settings.SEARCH_MAX_PER_PAGE}'}, + code='per_page_too_large', + ) + return value + + def validate(self, attrs: dict) -> dict: + """Validações cruzadas: de ≤ ate.""" + de: datetime | None = attrs.get('de') + ate: datetime | None = attrs.get('ate') + if de and ate and de > ate: + raise serializers.ValidationError( + {'error': 'invalid_date_range', + 'detail': '`de` deve ser ≤ `ate`'}, + code='invalid_date_range', + ) + return attrs + + def to_query_spec(self) -> QuerySpec: + """Converte para DTO frozen (após is_valid()).""" + data = self.validated_data + return QuerySpec( + q=data['q'], + author_id=data.get('author'), + category_id=data.get('category'), + de=data.get('de'), + ate=data.get('ate'), + cursor=data.get('cursor') or None, + per_page=data.get('per_page', settings.SEARCH_DEFAULT_PER_PAGE), + ) + + +# ── Output ─────────────────────────────────────────────────────────────────── + + +class _AuthorSerializer(serializers.Serializer): + id = serializers.CharField() + display_name = serializers.CharField() + slug = serializers.CharField() + + +class _CategorySerializer(serializers.Serializer): + id = serializers.IntegerField() + name = serializers.CharField() + slug = serializers.CharField() + + +class SearchResultItemSerializer(serializers.Serializer): + """Projeta :class:`ResultItem` no JSON do ADR-023.""" + + id = serializers.UUIDField(source='article_id') + title = serializers.CharField() + slug = serializers.CharField() + excerpt = serializers.CharField() + published_at = serializers.DateTimeField() + author = _AuthorSerializer() + category = _CategorySerializer(allow_null=True) + cover_url = serializers.CharField(allow_null=True) + score = serializers.FloatField() + + +class SearchResultPageSerializer(serializers.Serializer): + """Envelopa :class:`SearchResultPage` em resposta JSON. + + Inclui ``query_terms_expanded`` (Inv #11) para highlighting client-side + correto com stems pt-BR. + """ + + results = SearchResultItemSerializer(many=True) + next_cursor = serializers.CharField(allow_null=True) + total_estimate = serializers.IntegerField() + query_terms_expanded = serializers.ListField( + child=serializers.CharField(), + ) + took_ms = serializers.IntegerField() diff --git a/backend/apps/search/services.py b/backend/apps/search/services.py new file mode 100644 index 00000000..ed366a14 --- /dev/null +++ b/backend/apps/search/services.py @@ -0,0 +1,443 @@ +"""SearchService — query orquestrada full-text com 12 invariantes. + +Esta é a implementação canônica dos 12 invariantes do +``_specialist-outputs/02-algorithms-architect.md §8``. Cada bloco abaixo +referencia o número da invariante que ele defende. + +Fluxo (algorithms §7): + + 1. Decode cursor (Inv 5) — InvalidCursorError → 400 no view. + 2. Normalize q (Inv 2) — função única em ``apps.search.utils``. + 3. Validate token cap (Inv 8) — TooManyTokensError → 400. + 4. Empty-tsquery early-exit (Inv 7) — 0 hits Postgres. + 5. SQLite fallback: __icontains via Article queryset (ADR-020). + 6. Postgres: CTE candidates (LIMIT 500 M1) + scored (recency boost + Inv 10) + cursor tuple compare (Inv 6 ROUND 6). + 7. SET LOCAL statement_timeout (Inv 12). + 8. plainto_tsquery (Inv 3) — operadores ignorados. + 9. Status filter sempre (Inv 4) — drafts/agendados nunca vazam. + 10. Side-fetch Article side-relation via in_bulk (anti N+1). + 11. ts_lexize portuguese_stem → query_terms_expanded (Inv 11). + 12. estimate_total via plan_rows ou floor (ADR-025). + 13. Cap depth 50 (Inv 9) — encoded no novo cursor. + +Inv #1 (determinismo) emerge: DTOs frozen + função pura + ORDER BY +tie-breaker (score DESC, published_at DESC, article_id ASC) → mesma +input → mesma ordem. + +SECURITY: + - Queries SEMPRE via parametrização (`cursor.execute(sql, params)` + ou QuerySet .filter). NUNCA `.extra(where=)` ou `raw()` aqui. + (Comment-lock M-01 do SECURITY-REVIEW.) + - Response é function-pure de (q, filters, cursor). NÃO adicionar + campos por-usuário (bookmarked, read_history) sem rever ADR-037 + (H-04 cross-tier leak). +""" +from __future__ import annotations + +import logging +import time +import uuid +from datetime import datetime +from typing import Final + +from django.conf import settings +from django.db import connection, transaction + +from apps.articles.models import Article + +from .cursors import decode_cursor, encode_cursor +from .dto import CursorPayload, QuerySpec, ResultItem, SearchResultPage +from .utils import normalize_search_text + + +logger = logging.getLogger('interpop.search.service') + + +# Stopwords pt-BR mínimas para o early-exit Inv #7. Lista canônica vive +# no Postgres (`portuguese.stop`), mas precisamos de uma checagem barata +# em Python para abortar antes de bater no DB. Conjunto pequeno cobre o +# adversarial input típico ("o de da que e em"). +_PT_BR_STOPWORDS: Final[frozenset[str]] = frozenset({ + 'a', 'o', 'e', 'é', 'em', 'de', 'da', 'do', 'das', 'dos', + 'um', 'uma', 'uns', 'umas', 'que', 'se', 'no', 'na', 'nos', + 'nas', 'por', 'para', 'com', 'como', 'mas', 'ou', +}) + + +class TooManyTokensError(ValueError): + """Inv #8 — cap de tokens excedido. View traduz para 400 query_too_complex.""" + + +# ── Helpers públicos ───────────────────────────────────────────────────────── + + +def estimate_total( + *, results_len: int, per_page: int, plan_rows: int, page_count: int, +) -> int: + """ADR-025 — total_estimate com floor por evidência empírica. + + Args: + results_len: tamanho dos resultados desta página. + per_page: page size. + plan_rows: estimativa do EXPLAIN (heurística do planner). + page_count: nº da página atual (1-indexed). + + Returns: + ``max(plan_rows, (page_count - 1) * per_page + results_len)``. + + O floor garante que ``total_estimate`` nunca seja menor que a + evidência empírica de páginas já vistas (planner pode subestimar). + """ + floor = (page_count - 1) * per_page + results_len + return max(plan_rows, floor) + + +def _significant_tokens(normalized: str) -> list[str]: + """Tokens significativos = não stopwords e não vazios.""" + return [t for t in normalized.split() if t and t not in _PT_BR_STOPWORDS] + + +def _is_empty_tsquery(normalized: str) -> bool: + """Inv #7 — empty se string vazia OU só stopwords.""" + return not _significant_tokens(normalized) + + +# ── SearchService ──────────────────────────────────────────────────────────── + + +class SearchService: + """Serviço de busca editorial — entry point público (ADR-017). + + Sem Repository abstrato (DJango ORM já é Repository). Sem state + interno — instâncias podem ser criadas livremente; teste e prod + compartilham a mesma classe. + + Uso:: + + page = SearchService().query(QuerySpec(q='kpop')) + """ + + def query(self, spec: QuerySpec) -> SearchResultPage: + """Executa a busca conforme a spec. + + Erros conhecidos (view captura cada um para retornar 400): + + - :exc:`apps.search.cursors.InvalidCursorError` — cursor ruim. + - :exc:`TooManyTokensError` — > SEARCH_MAX_TOKENS tokens. + """ + t0 = time.perf_counter() + + # 1. Decode cursor — Inv 5 (InvalidCursorError propaga). + cursor_payload: CursorPayload | None = None + if spec.cursor: + cursor_payload = decode_cursor(spec.cursor) + + # 2. Normalize — Inv 2 (simétrico com signal e indexing). + q_norm = normalize_search_text(spec.q) + + # 3. Validação de tokens significativos — Inv 8. + sig_tokens = _significant_tokens(q_norm) + if len(sig_tokens) > settings.SEARCH_MAX_TOKENS: + raise TooManyTokensError( + f'{len(sig_tokens)} tokens significativos — ' + f'cap é {settings.SEARCH_MAX_TOKENS}' + ) + + # 4. Empty tsquery early-exit — Inv 7. + if _is_empty_tsquery(q_norm): + took_ms = int((time.perf_counter() - t0) * 1000) + return SearchResultPage( + results=(), + next_cursor=None, + total_estimate=0, + query_terms_expanded=(), + took_ms=took_ms, + ) + + # 5. Bifurca por vendor (ADR-020). + if connection.vendor != 'postgresql': + return self._query_sqlite_fallback(spec, q_norm, t0) + + # 6. Postgres path — CTE + recency + cursor + side-fetch. + return self._query_postgres(spec, q_norm, sig_tokens, cursor_payload, t0) + + # ── SQLite-dev fallback (ADR-020) ────────────────────────────────────── + + def _query_sqlite_fallback( + self, spec: QuerySpec, q_norm: str, t0: float, + ) -> SearchResultPage: + """Fallback __icontains para SQLite-dev. Sem FTS real, sem ranking + sofisticado — apenas garante que o pipeline roda end-to-end em dev. + + Mantém Inv #4 (status filter), Inv #1 (determinismo via ORDER BY). + """ + qs = ( + Article.objects.filter( + status=Article.Status.PUBLISHED, + published_at__isnull=False, + ) + .filter(title__icontains=spec.q) + .select_related('author', 'category') + .order_by('-published_at', 'id')[: spec.per_page + 1] + ) + rows = list(qs) + has_more = len(rows) > spec.per_page + rows = rows[: spec.per_page] + + items = tuple(_article_to_result(row, score=0.0) for row in rows) + took_ms = int((time.perf_counter() - t0) * 1000) + + next_cursor: str | None = None + if has_more and rows: + last = rows[-1] + next_cursor = encode_cursor(CursorPayload( + score=0.0, + published_at=last.published_at, + article_id=last.id, + depth=1, + )) + + return SearchResultPage( + results=items, + next_cursor=next_cursor, + total_estimate=estimate_total( + results_len=len(items), + per_page=spec.per_page, + plan_rows=len(items), # SQLite — sem EXPLAIN ROWS + page_count=1, + ), + query_terms_expanded=tuple(q_norm.split()), + took_ms=took_ms, + ) + + # ── Postgres path (algorithms §7 SQL completo) ───────────────────────── + + @transaction.atomic + def _query_postgres( + self, + spec: QuerySpec, + q_norm: str, + sig_tokens: list[str], + cursor: CursorPayload | None, + t0: float, + ) -> SearchResultPage: # pragma: no cover — exercitado em integration + """Path Postgres: CTE candidates + scored + cursor tuple compare. + + Fix F2-B-01 do REVIEW-PHASE-2: o método inteiro está dentro de + `@transaction.atomic` para que o `SET LOCAL statement_timeout` + aplicado em `_apply_statement_timeout` valha por toda a duração + (incluindo o main query, `_expand_stems` e `_explain_estimate`). + Sem a TX explícita, cada `with connection.cursor()` aninhado + criava sua própria TX implícita em autocommit, e o cap de 500ms + ficava ativo só durante a chamada `SET LOCAL` — Invariante #12 + quebrada em runtime. + """ + # Inv #12 — statement_timeout por TX (defesa T30.4.X9, independente + # do role Postgres). Vive dentro da TX criada por @transaction.atomic. + self._apply_statement_timeout() + + page_count = (cursor.depth + 1) if cursor else 1 + + # SECURITY M-01: queries com parametrização posicional. + sql = """ + WITH q AS ( + SELECT plainto_tsquery('portuguese', %(q_norm)s) AS query + ), + candidates AS ( + SELECT si.article_id, si.published_at, + si.author_id, si.category_id, + ts_rank_cd(si.search_vector, q.query, 32) AS rank_raw + FROM search_index si, q + WHERE + si.search_vector @@ q.query + AND q.query IS DISTINCT FROM ''::tsquery + AND (%(author_id)s::uuid IS NULL OR si.author_id = %(author_id)s::uuid) + AND (%(cat_id)s::bigint IS NULL OR si.category_id = %(cat_id)s::bigint) + AND (%(de)s::timestamptz IS NULL OR si.published_at >= %(de)s::timestamptz) + AND (%(ate)s::timestamptz IS NULL OR si.published_at <= %(ate)s::timestamptz) + ORDER BY rank_raw DESC + LIMIT %(candidates_limit)s + ), + scored AS ( + SELECT article_id, published_at, + ROUND( + (rank_raw * exp(-EXTRACT(EPOCH FROM (NOW() - published_at)) + / (86400.0 * %(half_life)s)))::numeric, 6 + )::float AS score + FROM candidates + ) + SELECT article_id, score, published_at + FROM scored + WHERE + %(cursor_score)s::float IS NULL + OR (score, published_at, article_id) + < (%(cursor_score)s::float, + %(cursor_pub)s::timestamptz, + %(cursor_id)s::uuid) + ORDER BY score DESC, published_at DESC, article_id ASC + LIMIT %(limit)s; + """ + + params = { + 'q_norm': q_norm, + 'author_id': str(spec.author_id) if spec.author_id else None, + 'cat_id': spec.category_id, + 'de': spec.de, + 'ate': spec.ate, + 'candidates_limit': settings.SEARCH_CANDIDATES_LIMIT, + 'half_life': settings.SEARCH_RECENCY_HALF_LIFE_DAYS, + 'cursor_score': cursor.score if cursor else None, + 'cursor_pub': cursor.published_at if cursor else None, + 'cursor_id': str(cursor.article_id) if cursor else None, + 'limit': spec.per_page + 1, # +1 para detectar has_more + } + + with connection.cursor() as cur: + cur.execute(sql, params) + rows = cur.fetchall() + + has_more = len(rows) > spec.per_page + rows = rows[: spec.per_page] + + # Side-fetch artigos (anti N+1). + ids = [r[0] for r in rows] + articles = ( + Article.objects.filter(id__in=ids) + .select_related('author', 'category') + .in_bulk(field_name='id') + ) + # Preserva ordem do ranking. + items: list[ResultItem] = [] + for article_id, score, _pub in rows: + article = articles.get(article_id) + if article is None: + continue # raríssimo (drift entre search_index e articles) + items.append(_article_to_result(article, score=score)) + + # Inv #11 — query_terms_expanded via ts_lexize portuguese_stem + terms = self._expand_stems(sig_tokens) + + # Próximo cursor (Inv 9 — cap em decode_cursor) + next_cursor: str | None = None + if has_more and rows: + last_id, last_score, last_pub = rows[-1] + next_cursor = encode_cursor(CursorPayload( + score=last_score, + published_at=last_pub, + article_id=uuid.UUID(str(last_id)), + depth=page_count, + )) + + # ADR-025 — total_estimate + plan_rows = self._explain_estimate(sql, params) + total = estimate_total( + results_len=len(items), + per_page=spec.per_page, + plan_rows=plan_rows, + page_count=page_count, + ) + + took_ms = int((time.perf_counter() - t0) * 1000) + return SearchResultPage( + results=tuple(items), + next_cursor=next_cursor, + total_estimate=total, + query_terms_expanded=tuple(terms), + took_ms=took_ms, + ) + + # ── Internal helpers ─────────────────────────────────────────────────── + + def _apply_statement_timeout(self) -> None: # pragma: no cover — Postgres + """Inv #12 — SET LOCAL statement_timeout (defesa T30.4.X9). + + CONTRATO: deve ser chamado de DENTRO de uma TX explícita (`@transaction.atomic` + ou `with transaction.atomic():`). `SET LOCAL` aplica ao TX atual e morre no + commit/rollback. Em autocommit (sem TX explícita), cada cursor abre uma TX + implícita própria — o SET LOCAL aplicado aqui morreria ao sair do `with` + e o main query rodaria sem cap. Esse foi o F2-B-01 do REVIEW-PHASE-2. + + Independente do role Postgres (defesa em profundidade — TX-15 ainda em backlog). + """ + ms = settings.SEARCH_STATEMENT_TIMEOUT_MS + with connection.cursor() as cur: + cur.execute(f"SET LOCAL statement_timeout = '{int(ms)}ms';") + + def _expand_stems(self, tokens: list[str]) -> list[str]: # pragma: no cover — Postgres + """Inv #11 — ts_lexize('portuguese_stem', token) por token.""" + if not tokens: + return [] + with connection.cursor() as cur: + cur.execute( + "SELECT ts_lexize('portuguese_stem', t) FROM unnest(%s::text[]) AS t;", + [tokens], + ) + rows = cur.fetchall() + # ts_lexize retorna text[] (None se stopword) + expanded: list[str] = [] + seen: set[str] = set() + for row in rows: + lex_array = row[0] or [] + for stem in lex_array: + if stem and stem not in seen: + seen.add(stem) + expanded.append(stem) + return expanded + + def _explain_estimate(self, sql: str, params: dict) -> int: # pragma: no cover — Postgres + """ADR-025 — extrai Plan Rows do EXPLAIN (FORMAT JSON).""" + try: + with connection.cursor() as cur: + cur.execute(f'EXPLAIN (FORMAT JSON) {sql}', params) + result = cur.fetchone() + if not result: + return 0 + plan = result[0][0]['Plan'] + return int(plan.get('Plan Rows', 0)) + except Exception: # noqa: BLE001 — EXPLAIN é best-effort + logger.exception('search.estimate.explain_failed') + return 0 + + +# ── ResultItem mapper ──────────────────────────────────────────────────────── + + +def _article_to_result(article: Article, *, score: float) -> ResultItem: + """Mapeia Article ORM → ResultItem frozen. + + Author/category são dicts pequenos (não entidades) — anti coupling. + """ + author = { + 'id': str(article.author_id), + 'display_name': ( + article.author.get_full_name() or article.author.username + ), + 'slug': getattr(article.author, 'username', ''), + } + category = None + if article.category_id: + category = { + 'id': article.category_id, + 'name': article.category.name, + 'slug': article.category.slug, + } + cover_url: str | None = None + if article.cover_image: + try: + cover_url = article.cover_image.url + except (ValueError, AttributeError): + cover_url = None + + published_at: datetime = article.published_at # type: ignore[assignment] + return ResultItem( + article_id=article.id, + title=article.title, + slug=article.slug, + excerpt=article.excerpt, + published_at=published_at, + author=author, + category=category, + cover_url=cover_url, + score=float(score), + ) diff --git a/backend/apps/search/signals.py b/backend/apps/search/signals.py new file mode 100644 index 00000000..5bdb5d3c --- /dev/null +++ b/backend/apps/search/signals.py @@ -0,0 +1,67 @@ +"""Signals do app de busca — invalidação de cache (Task T30.1.5c). + +**Invariante ADR-018 (DURA)**: este módulo NUNCA escreve em +``SearchIndex``. A sincronia ``Article → SearchIndex`` é feita pela +TRIGGER POSTGRES ``trg_articles_sync_search`` (migration 0003 + 0005 +ENABLE ALWAYS). Trigger é a fonte de verdade da consistência. + +O que este módulo faz: ao detectar mutação em ``Article`` (post_save ou +post_delete), apaga o cache Redis ``search:v1:*`` para forçar a próxima +request a recomputar a partir do banco já atualizado pela trigger. + +Não é necessário filtrar por ``status='published'`` aqui: + + - Rebaixar published → draft muda o conjunto de resultados. + - Adicionar draft cria potencial futura linha em search_index. + - Apagar published remove linha. + +Em todos os casos a busca anterior está stale. Invalidação total é +mais simples e segura que parcial neste estágio (volume de mutação +em Article é baixo — N artigos/dia). + +Referências: + + - ADR-018 §"Trigger = fonte de verdade; signal só cache invalidation" + - ADR-037 §"Cache key inclui auth_tier" — invalidação é total, não por + tier (rationale: tier afeta KEY, não conteúdo da resposta) + - SECURITY-REVIEW H-04 +""" +from __future__ import annotations + +import logging + +from django.db.models.signals import post_delete, post_save +from django.dispatch import receiver + +from apps.articles.models import Article + +from .cache import invalidate_all_search_cache + + +logger = logging.getLogger('interpop.search.signals') + + +@receiver(post_save, sender=Article, dispatch_uid='search.invalidate_on_save') +def _invalidate_on_article_save(sender, instance, created, **kwargs): + """Invalida cache em CADA save de Article (publish, edit, unpublish).""" + n = invalidate_all_search_cache() + logger.info( + 'search.cache.invalidated.on_save', + extra={ + 'article_id': str(instance.pk), + # 'created' colide com LogRecord.created — usar 'is_new'. + 'is_new': created, + 'status': instance.status, + 'keys_removed': n, + }, + ) + + +@receiver(post_delete, sender=Article, dispatch_uid='search.invalidate_on_delete') +def _invalidate_on_article_delete(sender, instance, **kwargs): + """Invalida cache em delete de Article (artigo some da projeção).""" + n = invalidate_all_search_cache() + logger.info( + 'search.cache.invalidated.on_delete', + extra={'article_id': str(instance.pk), 'keys_removed': n}, + ) diff --git a/backend/apps/search/tests/__init__.py b/backend/apps/search/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/search/tests/test_app_config.py b/backend/apps/search/tests/test_app_config.py new file mode 100644 index 00000000..2430a002 --- /dev/null +++ b/backend/apps/search/tests/test_app_config.py @@ -0,0 +1,82 @@ +"""Testes do bootstrap do app ``apps.search`` (Task T30.1.1). + +Asserções TDD para garantir que: + - O app está registrado em ``INSTALLED_APPS``. + - ``AppConfig`` foi carregado com nome correto. + - Os módulos esqueleto (models, services, dto, signals) importam sem erro. + - Os models ``SearchIndex`` e ``SearchLog`` têm ``Meta.managed = False`` + (a tabela é controlada por SQL puro nas migrations — ADR-018, ADR-019, + ADR-030-DB). +""" +from __future__ import annotations + +import importlib + +from django.apps import apps + + +def test_search_app_registered() -> None: + """T30.1.1 — apps.search deve aparecer em INSTALLED_APPS após registro.""" + assert apps.is_installed('apps.search'), ( + 'apps.search precisa estar registrado em INSTALLED_APPS ' + '(config/settings/base.py LOCAL_APPS).' + ) + + +def test_search_appconfig_loaded() -> None: + """T30.1.1 — SearchConfig deve carregar com nome e label corretos.""" + app_config = apps.get_app_config('search') + assert app_config.name == 'apps.search' + assert app_config.verbose_name == 'Busca editorial' + + +def test_skeleton_modules_importable() -> None: + """T30.1.2 — services, dto, signals devem importar sem erro (mesmo stubs).""" + for mod in ('services', 'dto', 'signals'): + importlib.import_module(f'apps.search.{mod}') + + +def test_search_index_model_is_unmanaged() -> None: + """ADR-018 — schema é controlado por SQL puro; ORM não pode achar que é dono.""" + from apps.search.models import SearchIndex + + assert SearchIndex._meta.managed is False, ( + 'SearchIndex.Meta.managed deve ser False — schema é controlado por ' + 'RunSQL (extensions, configuration, trigger, GIN). Ver ADR-018/019.' + ) + assert SearchIndex._meta.db_table == 'search_index' + + +def test_search_log_model_is_unmanaged() -> None: + """SearchLog também é criado por RunSQL na migration 0001.""" + from apps.search.models import SearchLog + + assert SearchLog._meta.managed is False + assert SearchLog._meta.db_table == 'search_log' + + +def test_search_index_author_id_is_uuid() -> None: + """Bug 1 do specialist DB — User.id é UUID, não BIGINT. + + DESIGN §2.2 corrige author_id BIGINT → UUID. Quebrar este test = regressão. + """ + from django.db import models as dj_models + + from apps.search.models import SearchIndex + + author_field = SearchIndex._meta.get_field('author_id') + assert isinstance(author_field, dj_models.UUIDField), ( + f'author_id deveria ser UUIDField (User.id é UUID), ' + f'mas é {type(author_field).__name__}.' + ) + + +def test_search_index_category_id_is_bigint_nullable() -> None: + """category_id continua BIGINT (Category usa BigAutoField padrão) e é NULL-able.""" + from django.db import models as dj_models + + from apps.search.models import SearchIndex + + cat_field = SearchIndex._meta.get_field('category_id') + assert isinstance(cat_field, dj_models.BigIntegerField) + assert cat_field.null is True diff --git a/backend/apps/search/tests/test_cache.py b/backend/apps/search/tests/test_cache.py new file mode 100644 index 00000000..107ea7b3 --- /dev/null +++ b/backend/apps/search/tests/test_cache.py @@ -0,0 +1,120 @@ +"""Testes do cache helper de busca (T30.4.X4 / SECURITY-REVIEW H-04). + +Cobre a invariante de **isolamento de cache por tier** (ADR-037): + + > Cache key inclui auth_tier ('anon' | 'user'). Resposta cacheada por + > usuário anônimo NUNCA serve para usuário autenticado e vice-versa. + +CWE-524 — sem essa separação, um campo personalizado adicionado em +release futura vaza entre tiers silenciosamente. +""" +from __future__ import annotations + +import uuid +from datetime import datetime, timezone + +from apps.search.cache import build_cache_key, canonical_query_string +from apps.search.dto import QuerySpec + + +# ── canonical_query_string ─────────────────────────────────────────────────── + + +def test_canonical_query_string_deterministic() -> None: + """Mesmo spec → mesma string canônica (defesa cache key estável).""" + aid = uuid.uuid4() + a = QuerySpec(q='kpop', author_id=aid, per_page=20) + b = QuerySpec(q='kpop', author_id=aid, per_page=20) + assert canonical_query_string(a) == canonical_query_string(b) + + +def test_canonical_query_string_includes_q_normalized() -> None: + """A string canônica usa o ``q`` normalizado (não cru) — defesa contra + cache thrashing por variantes case/whitespace.""" + a = QuerySpec(q='KPOP') + b = QuerySpec(q='kpop') + # Não exigimos string idêntica char a char, mas SIM cache key igual. + assert build_cache_key(a, auth_tier='anon') == build_cache_key(b, auth_tier='anon') + + +def test_canonical_query_string_includes_filters() -> None: + a = QuerySpec(q='kpop', category_id=3) + b = QuerySpec(q='kpop', category_id=5) + assert canonical_query_string(a) != canonical_query_string(b) + + +def test_canonical_query_string_cursor_excluded() -> None: + """Cursor NÃO entra na canonical — páginas 1/2/3 da mesma query devem + invalidar/atualizar a mesma família. Mas como o cache hit é por página, + o cursor entra na cache key separadamente (build_cache_key).""" + a = QuerySpec(q='kpop', cursor=None) + b = QuerySpec(q='kpop', cursor='abc.def') + assert canonical_query_string(a) == canonical_query_string(b) + + +# ── build_cache_key — H-04 (auth_tier separation) ──────────────────────────── + + +def test_cache_key_prefix() -> None: + """Key deve começar com ``search:v1:`` para casar com delete_pattern do + signal de invalidação.""" + key = build_cache_key(QuerySpec(q='kpop'), auth_tier='anon') + assert key.startswith('search:v1:') + + +def test_cache_key_includes_auth_tier_explicitly() -> None: + """H-04 — anon vs user produzem KEYS DIFERENTES para a mesma query. + + Sem isso, cache de anônimo pode servir autenticado (vazamento de + metadata de tier). É o coração do achado. + """ + spec = QuerySpec(q='kpop') + anon = build_cache_key(spec, auth_tier='anon') + user = build_cache_key(spec, auth_tier='user') + assert anon != user + assert ':anon:' in anon + assert ':user:' in user + + +def test_cache_key_rejects_unknown_tier() -> None: + """Defesa: tier desconhecido (typo) levanta erro em vez de cair em + string vazia silenciosa (que viraria 1 cache pool comum).""" + import pytest + with pytest.raises(ValueError): + build_cache_key(QuerySpec(q='kpop'), auth_tier='admin') # type: ignore[arg-type] + + +def test_cache_key_includes_cursor_for_pagination() -> None: + """Página 1 e página 2 da mesma query → cache keys distintas.""" + spec1 = QuerySpec(q='kpop', cursor=None) + spec2 = QuerySpec(q='kpop', cursor='abc.def') + k1 = build_cache_key(spec1, auth_tier='anon') + k2 = build_cache_key(spec2, auth_tier='anon') + assert k1 != k2 + + +def test_cache_key_is_sha256_64_hex() -> None: + """Key payload é SHA256 truncado para 64 hex (sem '/' problemático + em Redis pattern delete).""" + key = build_cache_key(QuerySpec(q='kpop'), auth_tier='anon') + # search:v1:: + _, _, tier, digest = key.split(':') + assert tier in ('anon', 'user') + # SHA256 = 64 hex chars + assert len(digest) == 64 + assert all(c in '0123456789abcdef' for c in digest) + + +def test_cache_key_includes_per_page() -> None: + """per_page=20 vs per_page=30 → keys distintas (paginação diferente, + response shape diferente).""" + a = build_cache_key(QuerySpec(q='kpop', per_page=20), auth_tier='anon') + b = build_cache_key(QuerySpec(q='kpop', per_page=30), auth_tier='anon') + assert a != b + + +def test_cache_key_includes_date_filters() -> None: + de = datetime(2026, 1, 1, tzinfo=timezone.utc) + a = build_cache_key(QuerySpec(q='kpop'), auth_tier='anon') + b = build_cache_key(QuerySpec(q='kpop', de=de), auth_tier='anon') + assert a != b diff --git a/backend/apps/search/tests/test_cursors.py b/backend/apps/search/tests/test_cursors.py new file mode 100644 index 00000000..1033dac8 --- /dev/null +++ b/backend/apps/search/tests/test_cursors.py @@ -0,0 +1,154 @@ +"""Testes do cursor HMAC base64 (T30.1.7 — Inv #5, #6, #9). + +Inv 5: cursor HMAC inválido → ValueError (400 no view, NÃO 500/200). +Inv 6: ROUND(score, 6) simétrico em encode/decode. +Inv 9: cap de depth (encode aceita até MAX; decode rejeita >MAX). + +Round-trip property: encode(payload) → decode → same payload. +""" +from __future__ import annotations + +import uuid +from datetime import datetime, timezone + +import pytest +from django.test import override_settings + +from apps.search.cursors import ( + InvalidCursorError, + decode_cursor, + encode_cursor, +) +from apps.search.dto import CursorPayload + + +@pytest.fixture +def sample_payload() -> CursorPayload: + return CursorPayload( + score=0.123456, + published_at=datetime(2026, 5, 1, 12, 0, 0, tzinfo=timezone.utc), + article_id=uuid.UUID('12345678-1234-1234-1234-123456789012'), + depth=1, + ) + + +# ── Round-trip ─────────────────────────────────────────────────────────────── + + +def test_encode_decode_round_trip(sample_payload: CursorPayload) -> None: + """Inv #5 — cursor válido roundtrips perfeitamente.""" + encoded = encode_cursor(sample_payload) + assert isinstance(encoded, str) + decoded = decode_cursor(encoded) + assert decoded == sample_payload + + +def test_encoded_is_base64_safe(sample_payload: CursorPayload) -> None: + """Cursor é base64 URL-safe (sem + / =).""" + encoded = encode_cursor(sample_payload) + assert all(c.isalnum() or c in '-_.' for c in encoded), ( + f'Cursor não é URL-safe: {encoded!r}' + ) + + +# ── Inv #6: ROUND(score, 6) simétrico ──────────────────────────────────────── + + +def test_score_rounded_to_6_decimals() -> None: + """Inv #6 — score com 12 casas é truncado em 6 (simétrico com SELECT + da CTE scored: ROUND(score::numeric, 6)).""" + payload = CursorPayload( + score=0.123456789012, + published_at=datetime.now(timezone.utc), + article_id=uuid.uuid4(), + depth=0, + ) + encoded = encode_cursor(payload) + decoded = decode_cursor(encoded) + # Float drift na 7ª casa é OK; até a 6ª deve casar. + assert round(decoded.score, 6) == round(payload.score, 6) + + +# ── Inv #5: assinatura inválida → InvalidCursorError ───────────────────────── + + +def test_decode_garbage_raises() -> None: + with pytest.raises(InvalidCursorError): + decode_cursor('not_a_cursor') + + +def test_decode_tampered_payload_raises(sample_payload: CursorPayload) -> None: + """Modificar 1 byte do payload sem refazer HMAC = invalid.""" + encoded = encode_cursor(sample_payload) + # Flip primeiro char do payload (antes do ponto) + payload_part, sig = encoded.split('.', 1) + flipped = chr(ord(payload_part[0]) ^ 1) + payload_part[1:] + tampered = f'{flipped}.{sig}' + with pytest.raises(InvalidCursorError): + decode_cursor(tampered) + + +def test_decode_tampered_signature_raises(sample_payload: CursorPayload) -> None: + encoded = encode_cursor(sample_payload) + payload_part, sig = encoded.split('.', 1) + flipped_sig = chr(ord(sig[0]) ^ 1) + sig[1:] + tampered = f'{payload_part}.{flipped_sig}' + with pytest.raises(InvalidCursorError): + decode_cursor(tampered) + + +def test_decode_wrong_secret_raises(sample_payload: CursorPayload) -> None: + """Cursor assinado com secret A não decodifica com secret B.""" + with override_settings(SEARCH_CURSOR_HMAC_SECRET='secret-a-' * 4): + encoded = encode_cursor(sample_payload) + with override_settings(SEARCH_CURSOR_HMAC_SECRET='secret-b-' * 4): + with pytest.raises(InvalidCursorError): + decode_cursor(encoded) + + +def test_decode_empty_raises() -> None: + with pytest.raises(InvalidCursorError): + decode_cursor('') + + +def test_decode_missing_separator_raises() -> None: + with pytest.raises(InvalidCursorError): + decode_cursor('abc_no_dot') + + +# ── Inv #9: cap depth ──────────────────────────────────────────────────────── + + +def test_decode_rejects_depth_over_cap() -> None: + """Inv #9 — depth > SEARCH_MAX_PAGINATION_DEPTH → InvalidCursorError.""" + over_depth = CursorPayload( + score=0.5, + published_at=datetime.now(timezone.utc), + article_id=uuid.uuid4(), + depth=999, # >> 50 + ) + encoded = encode_cursor(over_depth) + with pytest.raises(InvalidCursorError): + decode_cursor(encoded) + + +def test_decode_accepts_depth_at_cap() -> None: + at_cap = CursorPayload( + score=0.5, + published_at=datetime.now(timezone.utc), + article_id=uuid.uuid4(), + depth=50, + ) + encoded = encode_cursor(at_cap) + decoded = decode_cursor(encoded) + assert decoded.depth == 50 + + +# ── Uses hmac.compare_digest (timing-safe — L-04 SECURITY-REVIEW) ──────────── + + +def test_encode_is_deterministic(sample_payload: CursorPayload) -> None: + """Mesma payload + mesmo secret → mesmo cursor (defesa cache key).""" + a = encode_cursor(sample_payload) + b = encode_cursor(sample_payload) + assert a == b diff --git a/backend/apps/search/tests/test_dto.py b/backend/apps/search/tests/test_dto.py new file mode 100644 index 00000000..49b41a1e --- /dev/null +++ b/backend/apps/search/tests/test_dto.py @@ -0,0 +1,139 @@ +"""Testes dos DTOs imutáveis de ``apps.search.dto``. + +DTOs ``frozen=True`` reforçam a invariante #1 (determinismo): se a spec +não pode mudar depois de construída, o caller não consegue mutar input +e obter saída diferente "silenciosamente" — comportamento previsível +em testes property-based. +""" +from __future__ import annotations + +import uuid +from dataclasses import FrozenInstanceError +from datetime import datetime, timezone + +import pytest + +from apps.search.dto import ( + CursorPayload, + QuerySpec, + ResultItem, + SearchResultPage, +) + + +# ── QuerySpec ──────────────────────────────────────────────────────────────── + + +def test_query_spec_minimum_required() -> None: + spec = QuerySpec(q='kpop') + assert spec.q == 'kpop' + assert spec.author_id is None + assert spec.category_id is None + assert spec.cursor is None + + +def test_query_spec_full() -> None: + aid = uuid.uuid4() + de = datetime(2025, 1, 1, tzinfo=timezone.utc) + spec = QuerySpec( + q='soft power', + author_id=aid, + category_id=3, + de=de, + ate=None, + cursor='abc.def', + per_page=30, + ) + assert spec.author_id == aid + assert spec.category_id == 3 + assert spec.de == de + assert spec.per_page == 30 + + +def test_query_spec_is_frozen() -> None: + """Imutabilidade — proteção contra mutação out-of-band.""" + spec = QuerySpec(q='kpop') + with pytest.raises(FrozenInstanceError): + spec.q = 'mudei' # type: ignore[misc] + + +def test_query_spec_equality_value_based() -> None: + """Dois specs com mesmos campos são iguais (=cache key estável).""" + a = QuerySpec(q='kpop', per_page=20) + b = QuerySpec(q='kpop', per_page=20) + assert a == b + assert hash(a) == hash(b) + + +# ── CursorPayload ──────────────────────────────────────────────────────────── + + +def test_cursor_payload_round_trip_immutable() -> None: + pub = datetime(2026, 5, 1, 12, 0, tzinfo=timezone.utc) + aid = uuid.uuid4() + cur = CursorPayload(score=0.123456, published_at=pub, article_id=aid, depth=1) + assert cur.score == 0.123456 + assert cur.published_at == pub + assert cur.article_id == aid + assert cur.depth == 1 + + +def test_cursor_payload_frozen() -> None: + cur = CursorPayload( + score=0.5, + published_at=datetime.now(timezone.utc), + article_id=uuid.uuid4(), + depth=0, + ) + with pytest.raises(FrozenInstanceError): + cur.score = 0.9 # type: ignore[misc] + + +# ── ResultItem ─────────────────────────────────────────────────────────────── + + +def test_result_item_minimum() -> None: + pub = datetime(2026, 5, 1, tzinfo=timezone.utc) + aid = uuid.uuid4() + item = ResultItem( + article_id=aid, + title='Como o K-Pop reinventou o soft power', + slug='como-o-k-pop-reinventou-o-soft-power', + excerpt='Resumo curto', + published_at=pub, + author={'id': str(uuid.uuid4()), 'display_name': 'João', 'slug': 'joao'}, + category={'id': 3, 'name': 'Música', 'slug': 'musica'}, + cover_url=None, + score=0.473128, + ) + assert item.title.startswith('Como') + + +# ── SearchResultPage ───────────────────────────────────────────────────────── + + +def test_search_result_page_empty() -> None: + page = SearchResultPage( + results=(), + next_cursor=None, + total_estimate=0, + query_terms_expanded=(), + took_ms=12, + ) + assert page.results == () + assert page.next_cursor is None + assert page.total_estimate == 0 + + +def test_search_result_page_tuple_not_list() -> None: + """results e query_terms_expanded são tuples (imutáveis) — defesa contra + mutação de output após retornar.""" + page = SearchResultPage( + results=(), + next_cursor=None, + total_estimate=0, + query_terms_expanded=('cantor', 'brasil'), + took_ms=8, + ) + assert isinstance(page.results, tuple) + assert isinstance(page.query_terms_expanded, tuple) diff --git a/backend/apps/search/tests/test_migrations_0001.py b/backend/apps/search/tests/test_migrations_0001.py new file mode 100644 index 00000000..3e36ce3a --- /dev/null +++ b/backend/apps/search/tests/test_migrations_0001.py @@ -0,0 +1,193 @@ +"""Testes de schema da migration ``0001_initial`` do app ``apps.search``. + +Estratégia (ADR-020): + - Asserções "tabela existe" rodam em qualquer backend (Postgres + SQLite). + - Asserções FTS-específicas (extension, configuration, função IMMUTABLE, + tsvector real) são marcadas com ``@pytest.mark.requires_postgres`` e + ficam skip-se-não-Postgres. + +Refs: DESIGN.md §2.2; ADR-019; _specialist-outputs/01-database-architect.md §1. +""" +from __future__ import annotations + +import pytest +from django.db import connection + + +# ── Asserções cross-backend ──────────────────────────────────────────────────── + + +@pytest.mark.django_db +def test_search_index_table_exists() -> None: + """T30.1.4b — após migrate, tabela ``search_index`` deve existir.""" + table_names = connection.introspection.table_names() + assert 'search_index' in table_names, ( + f'Tabela search_index não foi criada pela migration 0001. ' + f'Tabelas presentes: {sorted(table_names)}' + ) + + +@pytest.mark.django_db +def test_search_log_table_exists() -> None: + """T30.1.4b — após migrate, tabela ``search_log`` deve existir.""" + table_names = connection.introspection.table_names() + assert 'search_log' in table_names + + +@pytest.mark.django_db +def test_search_index_columns_present() -> None: + """T30.1.4b — colunas chave da projeção devem existir. + + Em SQLite, os tipos podem diferir (CHAR(32) vs UUID), mas os nomes das + colunas são idênticos por contrato. + """ + cols = {col.name for col in connection.introspection.get_table_description( + connection.cursor(), 'search_index' + )} + expected = { + 'article_id', 'search_vector', 'title_text', 'excerpt_text', + 'body_text', 'author_id', 'category_id', 'published_at', 'indexed_at', + } + missing = expected - cols + assert not missing, f'Colunas ausentes em search_index: {missing}' + + +@pytest.mark.django_db +def test_search_log_columns_present() -> None: + """T30.1.4b — search_log tem campos para analytics + retenção LGPD.""" + cols = {col.name for col in connection.introspection.get_table_description( + connection.cursor(), 'search_log' + )} + expected = { + 'id', 'query_text', 'query_norm', 'filters_json', 'results_count', + 'total_estimate', 'duration_ms', 'cache_hit', 'user_id', 'created_at', + } + missing = expected - cols + assert not missing, f'Colunas ausentes em search_log: {missing}' + + +# ── Asserções Postgres-only (FTS pt-BR real) ─────────────────────────────────── + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_unaccent_extension_installed() -> None: + """ADR-019 — extension ``unaccent`` deve estar instalada em Postgres.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute("SELECT 1 FROM pg_extension WHERE extname = 'unaccent';") + assert cur.fetchone() is not None, ( + 'Extension unaccent não foi criada. Verifique permissão SUPERUSER ' + 'na primeira execução (TX-13).' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_pt_unaccent_configuration_exists() -> None: + """ADR-019 — CONFIGURATION ``public.pt_unaccent`` deve existir.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT 1 FROM pg_ts_config WHERE cfgname = 'pt_unaccent' " + "AND cfgnamespace = 'public'::regnamespace;" + ) + assert cur.fetchone() is not None, ( + 'CONFIGURATION pt_unaccent não criada. ADR-019 §Implementação.' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_articles_search_config_is_immutable() -> None: + """Bug 2 do specialist DB — provolatile deve ser 'i' (IMMUTABLE). + + Postgres recusa criar índice expressão sobre função STABLE/VOLATILE. Se este + test falhar, o GIN da migration 0002 vai falhar com 'functions in index + expression must be marked IMMUTABLE'. + """ + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT provolatile FROM pg_proc " + "WHERE proname = 'articles_search_config' " + "AND pronamespace = 'public'::regnamespace;" + ) + row = cur.fetchone() + assert row is not None, 'Função articles_search_config não criada.' + assert row[0] == 'i', ( + f"articles_search_config tem provolatile='{row[0]}' " + f"(esperado 'i' = IMMUTABLE). GIN vai falhar. ADR-019." + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_pt_unaccent_normalizes_accents() -> None: + """ADR-019 — 'Beyoncé' deve gerar mesmo lexema que 'beyonce'.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute("SELECT to_tsvector('public.pt_unaccent', %s)::text;", ['Beyoncé']) + with_accent = cur.fetchone()[0] + cur.execute("SELECT to_tsvector('public.pt_unaccent', %s)::text;", ['beyonce']) + without_accent = cur.fetchone()[0] + assert with_accent == without_accent, ( + f'pt_unaccent não normalizou: "{with_accent}" != "{without_accent}". ' + f'Mapping unaccent + portuguese_stem está errado em ADR-019.' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_pt_unaccent_stems_portuguese() -> None: + """ADR-019 — 'cantores' deve casar com lexema 'cantor' (portuguese_stem).""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT to_tsvector('public.pt_unaccent', %s) @@ " + "plainto_tsquery('public.pt_unaccent', %s);", + ['os cantores brasileiros', 'cantor'], + ) + assert cur.fetchone()[0] is True, ( + 'portuguese_stem não está ativo: "cantores" deveria casar com "cantor".' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_search_vector_column_is_tsvector() -> None: + """search_vector deve ser tsvector real em Postgres (não TEXT).""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT data_type FROM information_schema.columns " + "WHERE table_name = 'search_index' AND column_name = 'search_vector';" + ) + row = cur.fetchone() + assert row is not None and row[0] == 'tsvector', ( + f'search_vector deveria ser tsvector, é {row}' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_search_index_author_id_is_uuid_in_db() -> None: + """Bug 1 do specialist DB — author_id deve ser UUID, não BIGINT, no DB.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT data_type FROM information_schema.columns " + "WHERE table_name = 'search_index' AND column_name = 'author_id';" + ) + row = cur.fetchone() + assert row is not None and row[0] == 'uuid', ( + f'author_id deveria ser uuid no DB, é {row}. ' + f'Bug 1 do specialist DB foi reintroduzido.' + ) diff --git a/backend/apps/search/tests/test_migrations_0002.py b/backend/apps/search/tests/test_migrations_0002.py new file mode 100644 index 00000000..39360302 --- /dev/null +++ b/backend/apps/search/tests/test_migrations_0002.py @@ -0,0 +1,133 @@ +"""Testes da migration ``0002_search_indexes`` (Task T30.1.3 + ADR-030-DB). + +Todos os testes aqui são ``requires_postgres`` — SQLite não tem GIN nem +partial/covering indexes nos moldes Postgres. + +Verifica a presença dos 4 índices listados em ADR-030-DB. +""" +from __future__ import annotations + +import pytest +from django.db import connection + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_gin_index_on_search_vector_exists() -> None: + """ADR-030-DB §"Indexes finais" — idx_search_vector_gin.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT indexdef FROM pg_indexes " + "WHERE schemaname = 'public' AND indexname = 'idx_search_vector_gin';" + ) + row = cur.fetchone() + assert row is not None, 'GIN index idx_search_vector_gin não criado.' + assert 'USING gin' in row[0].lower(), ( + f'Index não é GIN: {row[0]!r}' + ) + assert 'search_vector' in row[0], ( + f'Index não cobre search_vector: {row[0]!r}' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_partial_category_index_exists() -> None: + """ADR-030-DB §"Por que parcial em category_id" — WHERE NOT NULL.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT indexdef FROM pg_indexes " + "WHERE schemaname = 'public' " + "AND indexname = 'idx_search_category_published';" + ) + row = cur.fetchone() + assert row is not None + definition = row[0].lower() + assert 'where' in definition, ( + f'Index não é parcial (sem WHERE): {row[0]!r}' + ) + assert 'category_id is not null' in definition, ( + f'Index parcial sem cláusula NOT NULL esperada: {row[0]!r}' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_covering_author_index_exists() -> None: + """ADR-030-DB §"Por que covering em author_id" — INCLUDE (article_id).""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT indexdef FROM pg_indexes " + "WHERE schemaname = 'public' " + "AND indexname = 'idx_search_author_pub_covering';" + ) + row = cur.fetchone() + assert row is not None + definition = row[0].lower() + assert 'include' in definition, ( + f'Index não é covering (sem INCLUDE): {row[0]!r}' + ) + assert 'article_id' in definition + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_published_at_btree_index_exists() -> None: + """idx_search_published_only — BTree para ordenação por recência.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + with connection.cursor() as cur: + cur.execute( + "SELECT indexdef FROM pg_indexes " + "WHERE schemaname = 'public' " + "AND indexname = 'idx_search_published_only';" + ) + assert cur.fetchone() is not None + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_all_four_indexes_present_on_search_index() -> None: + """Smoke: ADR-030-DB lista 4 índices; nenhum deles está faltando.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + expected = { + 'idx_search_vector_gin', + 'idx_search_category_published', + 'idx_search_author_pub_covering', + 'idx_search_published_only', + } + with connection.cursor() as cur: + cur.execute( + "SELECT indexname FROM pg_indexes " + "WHERE schemaname = 'public' AND tablename = 'search_index';" + ) + actual = {row[0] for row in cur.fetchall()} + missing = expected - actual + assert not missing, ( + f'Índices ADR-030-DB faltando: {missing}. Encontrados: {actual}' + ) + + +@pytest.mark.django_db +def test_migration_0002_runs_in_sqlite_dev_as_noop() -> None: + """ADR-020 — em SQLite, migration 0002 deve ser no-op (não trava).""" + if connection.vendor == 'postgresql': + pytest.skip('Esta asserção valida fallback SQLite.') + # Se chegamos aqui é porque migrate já rodou (db.sqlite3 existe) e a + # migration foi aplicada como no-op. Confirmar via django_migrations. + with connection.cursor() as cur: + cur.execute( + "SELECT 1 FROM django_migrations " + "WHERE app = 'search' AND name = '0002_search_indexes';" + ) + assert cur.fetchone() is not None, ( + 'Migration 0002 não foi aplicada em SQLite (deveria ser no-op ' + 'silencioso, não skip).' + ) diff --git a/backend/apps/search/tests/test_migrations_0003.py b/backend/apps/search/tests/test_migrations_0003.py new file mode 100644 index 00000000..405aa7b1 --- /dev/null +++ b/backend/apps/search/tests/test_migrations_0003.py @@ -0,0 +1,204 @@ +"""Testes da migration ``0003_search_triggers`` (Task T30.1.5b; ADR-018). + +Cenários cobertos (tabela de ADR-018 §"Tests obrigatórios"): + + 1. INSERT publicado → 1 linha em search_index com tsvector populado. + 2. UPDATE title em PUBLISHED → tsvector reflete novo título. + 3. UPDATE status='draft' (bulk_update) → search_index vazio. + 4. Raw SQL UPDATE status='draft' → search_index vazio. + 5. DELETE Article → search_index vazio. + +Todos os cenários são Postgres-only (trigger PL/pgSQL não roda em SQLite). +""" +from __future__ import annotations + +import uuid +from datetime import datetime, timezone + +import pytest +from django.db import connection + +from apps.articles.models import Article, Category + + +# ── Helpers ──────────────────────────────────────────────────────────────────── + + +def _count_search_index(article_id: uuid.UUID) -> int: + with connection.cursor() as cur: + cur.execute( + 'SELECT COUNT(*) FROM search_index WHERE article_id = %s;', + [str(article_id)], + ) + return cur.fetchone()[0] + + +def _get_search_vector_text(article_id: uuid.UUID) -> str | None: + with connection.cursor() as cur: + cur.execute( + 'SELECT search_vector::text FROM search_index WHERE article_id = %s;', + [str(article_id)], + ) + row = cur.fetchone() + return row[0] if row else None + + +@pytest.fixture +def author(admin_user): + """Usa fixture global admin_user (apps/users/conftest.py) como autor.""" + return admin_user + + +@pytest.fixture +def category(db): + # get_or_create porque migration 0003 do app articles já popula categorias + # (Música, Cinema, Moda, Literatura, Cultura Digital). + cat, _ = Category.objects.get_or_create(name='Música Teste FTS') + return cat + + +# ── Cenários ADR-018 ─────────────────────────────────────────────────────────── + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_publish_article_inserts_into_search_index(author, category) -> None: + """ADR-018 — INSERT publicado deve criar 1 linha em search_index.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only — trigger PL/pgSQL') + article = Article.objects.create( + title='K-Pop e a geopolítica do som', + excerpt='Como BTS reescreveu o soft power coreano.', + body='Em 2012, o vídeo de Gangnam Style ultrapassou 1 bilhão de views...', + author=author, + category=category, + status=Article.Status.PUBLISHED, + published_at=datetime.now(timezone.utc), + ) + assert _count_search_index(article.id) == 1, ( + 'Trigger não criou linha em search_index após publicação.' + ) + vec = _get_search_vector_text(article.id) + assert vec is not None and len(vec) > 0, ( + 'search_vector está vazio — setweight não aplicou.' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_update_title_in_published_refreshes_vector(author, category) -> None: + """ADR-018 — UPDATE title em PUBLISHED deve atualizar tsvector.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + article = Article.objects.create( + title='Título original', + excerpt='Excerpt', + body='Body', + author=author, + category=category, + status=Article.Status.PUBLISHED, + published_at=datetime.now(timezone.utc), + ) + vec_before = _get_search_vector_text(article.id) + article.title = 'Título completamente diferente sobre Beyoncé' + article.save() + vec_after = _get_search_vector_text(article.id) + assert vec_before != vec_after, ( + 'tsvector não mudou após UPDATE title — trigger UPDATE não disparou.' + ) + # 'beyonce' (sem acento via pt_unaccent) deve estar no vetor após o UPDATE. + assert 'beyonc' in vec_after.lower(), ( + f'Lexema "beyonc" ausente no novo vetor: {vec_after}' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_bulk_update_status_to_draft_removes_from_index(author, category) -> None: + """ADR-018 — bulk_update / QuerySet.update() devem disparar trigger. + + Esse é o cenário que signal Python NÃO cobre (Bug 3 do specialist DB). + """ + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + article = Article.objects.create( + title='Para despublicar', + excerpt='X', + body='Y', + author=author, + category=category, + status=Article.Status.PUBLISHED, + published_at=datetime.now(timezone.utc), + ) + assert _count_search_index(article.id) == 1 + # QuerySet.update() — NÃO dispara signal Django, mas DEVE disparar trigger. + Article.objects.filter(pk=article.pk).update(status=Article.Status.DRAFT) + assert _count_search_index(article.id) == 0, ( + 'search_index ainda tem linha após bulk update para draft — trigger ' + 'NÃO cobriu o caso. Bug 3 reintroduzido.' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_raw_sql_update_status_removes_from_index(author, category) -> None: + """ADR-018 — UPDATE direto via raw SQL deve disparar trigger.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + article = Article.objects.create( + title='Raw SQL test', + excerpt='X', + body='Y', + author=author, + category=category, + status=Article.Status.PUBLISHED, + published_at=datetime.now(timezone.utc), + ) + assert _count_search_index(article.id) == 1 + with connection.cursor() as cur: + cur.execute( + "UPDATE articles SET status = 'draft' WHERE id = %s;", + [str(article.id)], + ) + assert _count_search_index(article.id) == 0, ( + 'search_index ainda tem linha após raw SQL — trigger é a única defesa ' + 'aqui, e falhou.' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_delete_article_removes_from_index(author, category) -> None: + """ADR-018 — DELETE Article deve remover de search_index.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + article = Article.objects.create( + title='Para deletar', + excerpt='X', + body='Y', + author=author, + category=category, + status=Article.Status.PUBLISHED, + published_at=datetime.now(timezone.utc), + ) + article_id = article.id + assert _count_search_index(article_id) == 1 + article.delete() + assert _count_search_index(article_id) == 0, ( + 'search_index ainda tem linha após DELETE Article — trigger ' + 'remove_search não disparou.' + ) + + +# ── Smoke cross-backend ──────────────────────────────────────────────────────── + + +@pytest.mark.django_db +def test_migration_0003_applied_in_any_backend() -> None: + """ADR-020 — migration 0003 deve estar marcada como aplicada (no-op em SQLite).""" + with connection.cursor() as cur: + cur.execute( + "SELECT 1 FROM django_migrations " + "WHERE app = 'search' AND name = '0003_search_triggers';" + ) + assert cur.fetchone() is not None diff --git a/backend/apps/search/tests/test_migrations_0004.py b/backend/apps/search/tests/test_migrations_0004.py new file mode 100644 index 00000000..a7d9d959 --- /dev/null +++ b/backend/apps/search/tests/test_migrations_0004.py @@ -0,0 +1,80 @@ +"""Testes da migration ``0004_search_vacuum_tuning`` (Task T30.1.X1; ADR-034). + +Verifica que os storage params customizados foram aplicados ao índice GIN e à +tabela search_index. Todos os testes Postgres-only (SQLite não tem autovacuum +nem gin_pending_list_limit). +""" +from __future__ import annotations + +import pytest +from django.db import connection + + +def _get_reloptions(rel_name: str) -> dict[str, str]: + """Lê pg_class.reloptions de uma tabela/índice como dict.""" + with connection.cursor() as cur: + cur.execute( + "SELECT reloptions FROM pg_class WHERE relname = %s;", + [rel_name], + ) + row = cur.fetchone() + if row is None or row[0] is None: + return {} + options = {} + for entry in row[0]: + key, _, val = entry.partition('=') + options[key] = val + return options + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_gin_index_has_fastupdate_on() -> None: + """ADR-034 — idx_search_vector_gin.fastupdate = on.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + opts = _get_reloptions('idx_search_vector_gin') + assert opts.get('fastupdate') == 'on', ( + f'fastupdate não está on em idx_search_vector_gin. Reloptions: {opts}' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_gin_index_has_pending_list_limit_2mb() -> None: + """ADR-034 — idx_search_vector_gin.gin_pending_list_limit = 2048 (2MB em KB).""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + opts = _get_reloptions('idx_search_vector_gin') + assert opts.get('gin_pending_list_limit') == '2048', ( + f'gin_pending_list_limit esperado 2048 (2MB), atual: {opts}' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_search_index_table_autovacuum_aggressive() -> None: + """ADR-034 — autovacuum scale_factor 0.05 / analyze 0.02 / cost_delay 10ms.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + opts = _get_reloptions('search_index') + assert opts.get('autovacuum_vacuum_scale_factor') == '0.05', ( + f'autovacuum_vacuum_scale_factor esperado 0.05, atual: {opts}' + ) + assert opts.get('autovacuum_analyze_scale_factor') == '0.02', ( + f'autovacuum_analyze_scale_factor esperado 0.02, atual: {opts}' + ) + assert opts.get('autovacuum_vacuum_cost_delay') == '10', ( + f'autovacuum_vacuum_cost_delay esperado 10ms, atual: {opts}' + ) + + +@pytest.mark.django_db +def test_migration_0004_applied() -> None: + """ADR-020 — migration 0004 marcada como aplicada (no-op em SQLite).""" + with connection.cursor() as cur: + cur.execute( + "SELECT 1 FROM django_migrations " + "WHERE app = 'search' AND name = '0004_search_vacuum_tuning';" + ) + assert cur.fetchone() is not None diff --git a/backend/apps/search/tests/test_migrations_0005.py b/backend/apps/search/tests/test_migrations_0005.py new file mode 100644 index 00000000..bf21f5d5 --- /dev/null +++ b/backend/apps/search/tests/test_migrations_0005.py @@ -0,0 +1,151 @@ +"""Testes da migration ``0005_trigger_enable_always`` (Task T30.1.5d; ADR-039). + +Fecha o vetor M-04/H-04 do SECURITY-REVIEW (CWE-863): trigger criada com +``ENABLE`` default (modo ORIGIN) é bypassada por +``SET session_replication_role = 'replica'``. ``ENABLE ALWAYS`` força execução +mesmo em modo replica. + +Cenários cobertos: + + 1. ``pg_trigger.tgenabled = 'A'`` para ``articles_sync_search`` e + ``articles_remove_search`` (estrutural — verifica o estado da migration). + 2. Funcional — em sessão com ``session_replication_role = 'replica'``, + INSERT publicado AINDA popula ``search_index``. Sem ENABLE ALWAYS, + o INSERT seria silencioso (trigger não dispara) e ``search_index`` + ficaria vazio. + +Referências: + + - ADR-039 (Test integration trigger bypass session_replication_role) + - REVIEW-PHASE-1.md §3 H-01 + - SECURITY-REVIEW.md M-04 +""" +from __future__ import annotations + +import uuid + +import pytest +from django.db import connection + +from apps.articles.models import Category + + +def _trigger_enabled(trigger_name: str) -> str: + """Lê ``pg_trigger.tgenabled`` para um trigger por nome. + + Valores possíveis: + - 'O' (Origin) — default; bypassado por session_replication_role='replica' + - 'A' (Always) — dispara mesmo em modo replica (defesa em profundidade) + - 'D' (Disabled), 'R' (Replica only) + """ + with connection.cursor() as cur: + cur.execute( + 'SELECT tgenabled FROM pg_trigger WHERE tgname = %s;', + [trigger_name], + ) + row = cur.fetchone() + return row[0] if row else '' + + +def _count_search_index(article_id: uuid.UUID) -> int: + with connection.cursor() as cur: + cur.execute( + 'SELECT COUNT(*) FROM search_index WHERE article_id = %s;', + [str(article_id)], + ) + return cur.fetchone()[0] + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_sync_trigger_is_enable_always() -> None: + """ADR-039 — trigger ``articles_sync_search`` deve ter ``tgenabled = 'A'``.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only — pg_trigger é catálogo Postgres') + state = _trigger_enabled('articles_sync_search') + assert state == 'A', ( + f"articles_sync_search deve estar em ENABLE ALWAYS (tgenabled='A'), " + f"está '{state}'. Sem isso, SET session_replication_role='replica' " + f"bypassa a trigger (vetor M-04 do SECURITY-REVIEW)." + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_remove_trigger_is_enable_always() -> None: + """ADR-039 — trigger ``articles_remove_search`` também deve ser ALWAYS.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only — pg_trigger é catálogo Postgres') + state = _trigger_enabled('articles_remove_search') + assert state == 'A', ( + f"articles_remove_search deve estar em ENABLE ALWAYS, está '{state}'." + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db(transaction=True) +def test_trigger_fires_under_replica_role(admin_user) -> None: + """Funcional — sob ``session_replication_role='replica'``, o INSERT em + artigo publicado AINDA popula ``search_index`` (porque a trigger é ALWAYS). + + Esta é a defesa real contra o vetor de bypass: + 1. Atacante com role REPLICATION (ou superuser) executa + ``SET session_replication_role = 'replica'``. + 2. Triggers ORIGIN (default ENABLE) NÃO disparam → INSERT silencioso + em articles, search_index fica vazio → drift permanente. + 3. Com ENABLE ALWAYS, a trigger dispara mesmo assim → consistência + preservada. + """ + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only — session_replication_role é Postgres') + + category, _ = Category.objects.get_or_create(name='Música Replica Test') + + # Simula atacante / replica session + with connection.cursor() as cur: + cur.execute("SET LOCAL session_replication_role = 'replica';") + # INSERT direto via SQL (Django ORM via SET LOCAL é tricky em mesma tx) + article_id = uuid.uuid4() + cur.execute( + """ + INSERT INTO articles ( + id, title, slug, excerpt, body, author_id, category_id, + status, is_featured, view_count, created_at, updated_at, + published_at, cover_caption + ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, + 'published', false, 0, NOW(), NOW(), NOW(), '' + ); + """, + [ + str(article_id), + 'Trigger ENABLE ALWAYS deve disparar mesmo em replica', + f'trigger-replica-{article_id.hex[:8]}', + 'Excerpt sob replica role', + 'Body sob replica role', + str(admin_user.id), + category.id, + ], + ) + + assert _count_search_index(article_id) == 1, ( + "Trigger não disparou sob session_replication_role='replica'. " + "ADR-039 / migration 0005 não aplicada ou trigger não é ENABLE ALWAYS. " + "Vetor M-04 do SECURITY-REVIEW REABERTO." + ) + + +@pytest.mark.django_db +def test_migration_0005_applied() -> None: + """Estrutural — a migration 0005 deve existir e estar registrada. + + Não requer Postgres porque só verifica o estado de migration recorder. + Em SQLite-dev a migration é no-op (não há pg_trigger). + """ + from django.db.migrations.recorder import MigrationRecorder + + applied = MigrationRecorder(connection).applied_migrations() + assert ('search', '0005_trigger_enable_always') in applied, ( + 'Migration 0005_trigger_enable_always não aplicada — rodar `uv run ' + 'python manage.py migrate search`.' + ) diff --git a/backend/apps/search/tests/test_service.py b/backend/apps/search/tests/test_service.py new file mode 100644 index 00000000..ea7a1825 --- /dev/null +++ b/backend/apps/search/tests/test_service.py @@ -0,0 +1,211 @@ +"""Testes do SearchService.query() — invariantes do algorithms specialist. + +Cobertura (TDD em camadas): + +Invariantes testáveis SEM Postgres (SQLite-dev OK): + - Inv #7: empty tsquery early-exit (0 DB hits) + - Inv #8: cap 8 tokens (>8 levanta erro) + - Inv #11: query_terms_expanded computado (mesmo em fallback SQLite) + - estimate_total: floor formula (ADR-025) + - has_meaningful_query: stopwords sozinhas → empty + +Invariantes que EXIGEM Postgres (marker requires_postgres): + - Inv #3: plainto_tsquery (não to_tsquery) + - Inv #4: status='published' AND published_at IS NOT NULL filter + - Inv #6: cursor estável sob inserts concorrentes + - SQL CTE candidates LIMIT 500 + recency boost +""" +from __future__ import annotations + +from unittest.mock import patch + +import pytest +from django.test import override_settings + +from apps.search.dto import QuerySpec +from apps.search.services import ( + SearchService, + TooManyTokensError, + estimate_total, +) + + +# ── estimate_total (ADR-025) ───────────────────────────────────────────────── + + +def test_estimate_total_takes_max_of_plan_and_floor() -> None: + """ADR-025 — total_estimate = max(plan_rows, (page_count-1)*per_page+len(results)).""" + # 1 página, 5 resultados, plan diz 100 → max(100, 0+5) = 100 + assert estimate_total(results_len=5, per_page=20, plan_rows=100, page_count=1) == 100 + # 3 páginas, 20 resultados na última, plan diz 30 → max(30, 40+20=60) = 60 + assert estimate_total(results_len=20, per_page=20, plan_rows=30, page_count=3) == 60 + # 1 página, 0 resultados → max(0, 0) = 0 + assert estimate_total(results_len=0, per_page=20, plan_rows=0, page_count=1) == 0 + + +def test_estimate_total_floor_dominates_when_plan_underestimates() -> None: + """Plan rows é heurística; nunca pode ser menor que evidência empírica + de pages já vistas.""" + # 10ª página com 15 resultados → floor = (10-1)*20 + 15 = 195 + # plan diz 50 (underestima) → max(50, 195) = 195 + assert estimate_total(results_len=15, per_page=20, plan_rows=50, page_count=10) == 195 + # Mesma página, plan diz 999 (acima do floor) → max(999, 195) = 999 + assert estimate_total(results_len=15, per_page=20, plan_rows=999, page_count=10) == 999 + + +# ── Inv #7: empty tsquery early-exit ───────────────────────────────────────── + + +@pytest.mark.django_db +def test_empty_q_early_exits_zero_db_hits() -> None: + """Inv #7 — q apenas stopwords ou pontuação → 0 queries Postgres. + + Defesa contra adversarial input que infla bitmap intersection. + Testamos via CaptureQueriesContext: depois do early-exit, ZERO queries. + """ + from django.test.utils import CaptureQueriesContext + from django.db import connection + + service = SearchService() + spec = QuerySpec(q='!@#$') # só pontuação — normaliza para '' + + with CaptureQueriesContext(connection) as ctx: + page = service.query(spec) + + assert page.results == () + assert page.next_cursor is None + assert page.total_estimate == 0 + # Zero queries Postgres — o service nem foi ao DB. + assert len(ctx.captured_queries) == 0, ( + f'Empty tsquery deveria ter early-exit (0 queries), mas executou ' + f'{len(ctx.captured_queries)}: {[q["sql"][:80] for q in ctx.captured_queries]}' + ) + + +@pytest.mark.django_db +def test_empty_q_returns_query_terms_expanded_empty() -> None: + """Empty early-exit ainda devolve query_terms_expanded=() (Inv #11 honra + o contrato de shape).""" + service = SearchService() + page = service.query(QuerySpec(q='!@#$%')) + assert page.query_terms_expanded == () + + +# ── Inv #8: cap 8 tokens ───────────────────────────────────────────────────── + + +@override_settings(SEARCH_MAX_TOKENS=8) +def test_more_than_8_tokens_raises_too_many() -> None: + """Inv #8 — > 8 tokens significativos → TooManyTokensError (view 400). + + Não precisa de DB — o cap é validado ANTES do early-exit/query. + """ + nine_tokens = 'kpop bts blackpink redvelvet twice itzy aespa mamamoo nine' + spec = QuerySpec(q=nine_tokens) + service = SearchService() + with pytest.raises(TooManyTokensError): + service.query(spec) + + +@pytest.mark.django_db +@override_settings(SEARCH_MAX_TOKENS=8) +def test_exactly_8_tokens_passes() -> None: + """8 é o cap — passa, 9 não.""" + eight_tokens = 'kpop bts blackpink redvelvet twice itzy aespa mamamoo' + spec = QuerySpec(q=eight_tokens) + service = SearchService() + # Não deve raise — pode retornar resultados vazios em SQLite + page = service.query(spec) + assert page is not None + + +# ── Inv #11: query_terms_expanded ──────────────────────────────────────────── + + +@pytest.mark.django_db +def test_query_terms_expanded_returns_tuple_of_strings() -> None: + """Inv #11 — response.query_terms_expanded é tuple[str, ...] (frozen).""" + service = SearchService() + page = service.query(QuerySpec(q='kpop')) + assert isinstance(page.query_terms_expanded, tuple) + assert all(isinstance(t, str) for t in page.query_terms_expanded) + + +# ── SQLite fallback (icontains) — DESIGN §3.6 ──────────────────────────────── + + +@pytest.mark.django_db +def test_sqlite_fallback_used_when_vendor_not_postgres() -> None: + """ADR-020 — em SQLite-dev, query usa __icontains, não FTS.""" + from django.db import connection + + if connection.vendor == 'postgresql': + pytest.skip('Este test cobre fallback SQLite — pula em Postgres') + + service = SearchService() + page = service.query(QuerySpec(q='kpop')) + # No SQLite dev sem fixtures, espera 0 resultados (sem crash). + assert page.results == () + assert page.total_estimate == 0 + + +# ── Cursor inválido propaga InvalidCursorError ─────────────────────────────── + + +@pytest.mark.django_db +def test_invalid_cursor_propagates_error() -> None: + """Inv #5 — cursor inválido → InvalidCursorError (view traduz 400).""" + from apps.search.cursors import InvalidCursorError + + service = SearchService() + spec = QuerySpec(q='kpop', cursor='garbage') + with pytest.raises(InvalidCursorError): + service.query(spec) + + +# ── Inv #3: plainto_tsquery (Postgres-only) ────────────────────────────────── + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_uses_plainto_tsquery_not_to_tsquery() -> None: + """Inv #3 — operadores tsquery (& | ! :*) são IGNORADOS por plainto. + + plainto_tsquery('kpop:*&!') = plainto_tsquery('kpop') (limpa operadores). + Sem isso, atacante poderia injetar operadores de tsquery. + """ + from django.db import connection + + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + + service = SearchService() + # Nenhum erro — plainto sanitiza + page = service.query(QuerySpec(q='kpop:*&!|')) + assert page is not None # smoke + + +# ── statement_timeout aplicado (Inv #12) ───────────────────────────────────── + + +@pytest.mark.requires_postgres +@pytest.mark.django_db +def test_statement_timeout_applied_per_tx() -> None: + """Inv #12 — SET LOCAL statement_timeout = '500ms' por transação. + + Defesa em profundidade independente do role Postgres. + """ + from django.db import connection + + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only') + + service = SearchService() + with patch.object(service, '_apply_statement_timeout') as mock_apply: + service.query(QuerySpec(q='kpop')) + # Foi chamado (defesa T30.4.X9) + assert mock_apply.called or page_was_early_exit_check() + + +def page_was_early_exit_check() -> bool: # helper para legibilidade do test acima + return False diff --git a/backend/apps/search/tests/test_settings.py b/backend/apps/search/tests/test_settings.py new file mode 100644 index 00000000..7ee35433 --- /dev/null +++ b/backend/apps/search/tests/test_settings.py @@ -0,0 +1,89 @@ +"""Testes das constantes ``SEARCH_*`` em ``config/settings/base.py``. + +Cada constante mapeia para uma invariante do algorithms specialist +(``_specialist-outputs/02-algorithms-architect.md §8``). Mudança silenciosa +no settings sem atualizar esta lista = mudança de invariante sem revisão. + +Cobertura: + - Inv 10: ``SEARCH_RECENCY_HALF_LIFE_DAYS = 60`` + - Inv 8: ``SEARCH_MAX_TOKENS = 8`` + - Inv 9: ``SEARCH_MAX_PAGINATION_DEPTH = 50`` + - Inv 12: ``SEARCH_STATEMENT_TIMEOUT_MS = 500`` + - DESIGN §2.4: paginação default 20 / max 50 + - TX-13: ``SEARCH_FEATURE_ENABLED`` default False + - HMAC: ``SEARCH_CURSOR_HMAC_SECRET`` não vazio + - ADR-024 + ADR-036: throttle scopes registrados +""" +from __future__ import annotations + +from django.conf import settings + + +def test_recency_half_life_days() -> None: + """Inv 10 — half-life em settings, não literal na SQL.""" + assert settings.SEARCH_RECENCY_HALF_LIFE_DAYS == 60 + + +def test_max_tokens_cap() -> None: + """Inv 8 — cap de 8 tokens significativos.""" + assert settings.SEARCH_MAX_TOKENS == 8 + + +def test_q_length_bounds() -> None: + """DESIGN §2.4 — 2 ≤ len(q) ≤ 200.""" + assert settings.SEARCH_MIN_Q_LENGTH == 2 + assert settings.SEARCH_MAX_Q_LENGTH == 200 + + +def test_pagination_depth_cap() -> None: + """Inv 9 — cap de profundidade 50 (cursor carrega depth).""" + assert settings.SEARCH_MAX_PAGINATION_DEPTH == 50 + + +def test_per_page_default_and_max() -> None: + """DESIGN §2.4 — per_page default 20, max 50.""" + assert settings.SEARCH_DEFAULT_PER_PAGE == 20 + assert settings.SEARCH_MAX_PER_PAGE == 50 + + +def test_candidates_limit() -> None: + """M1 algorithms §2.3 — CTE candidate-narrowing LIMIT 500.""" + assert settings.SEARCH_CANDIDATES_LIMIT == 500 + + +def test_statement_timeout_ms() -> None: + """Inv 12 — statement_timeout aplicado por TX no service.""" + assert settings.SEARCH_STATEMENT_TIMEOUT_MS == 500 + + +def test_feature_flag_default_off() -> None: + """T30.1.X4 — flag off por default (cutover deliberado em prod).""" + assert settings.SEARCH_FEATURE_ENABLED is False + + +def test_cursor_hmac_secret_non_empty() -> None: + """Cursor HMAC secret não pode ser vazio (TX-01) — fail-hard se vazio.""" + assert settings.SEARCH_CURSOR_HMAC_SECRET + assert len(settings.SEARCH_CURSOR_HMAC_SECRET) >= 16 + + +def test_throttle_scopes_registered() -> None: + """ADR-024 + ADR-036 — três scopes da busca em DEFAULT_THROTTLE_RATES. + + NB: ``development.py`` sobrescreve REST_FRAMEWORK inteiro (não merge), + então este test apenas verifica presença das chaves — os valores + são relaxados em dev (10000/hour) vs prod (30/min, 60/min, 500/min). + Valores de prod estão em ``base.py`` e são lidos por env var. + """ + rates = settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] + assert 'search_anon' in rates, ( + 'Scope search_anon ausente — adicionar tanto em base.py quanto em ' + 'development.py / production.py (REST_FRAMEWORK é replaced, não merged).' + ) + assert 'search_user' in rates + assert 'search_global' in rates + + +def test_cache_ttl_seconds() -> None: + """Redis cache TTL — 5 min default (alinha Cache-Control + Redis).""" + assert settings.SEARCH_CACHE_TTL_SECONDS == 300 diff --git a/backend/apps/search/tests/test_settings_production.py b/backend/apps/search/tests/test_settings_production.py new file mode 100644 index 00000000..9ae5f097 --- /dev/null +++ b/backend/apps/search/tests/test_settings_production.py @@ -0,0 +1,121 @@ +"""Production settings guards (fixes F2-B-03 / REVIEW-PHASE-2). + +Por que existe: `base.py` deixa `SEARCH_CURSOR_HMAC_SECRET` cair em +`SECRET_KEY` como fallback (conveniente em dev). `production.py` precisa +recusar essa configuração — leak de SECRET_KEY (via dump, traceback ou +dep comprometida) permite forjar cursor e bypassar o cap de 50 páginas +(A3 do specialist algorithms). + +Estratégia de teste: importação dinâmica de `config.settings.production` +sob diferentes envs, capturando `ImproperlyConfigured`. Esta abordagem é +necessária porque settings já está carregado pela sessão atual de +pytest — `importlib.reload` quebra apps; usamos `subprocess` para +isolar o teste em um interpretador fresco. +""" +from __future__ import annotations + +import os +import subprocess +import sys +import textwrap +from pathlib import Path + + +# Backend root = …/interpop/backend (contém manage.py + config/). +# Sobe de apps/search/tests/test_settings_production.py. +_BACKEND_ROOT = Path(__file__).resolve().parents[3] + + +def _run_settings_load(env: dict[str, str]) -> subprocess.CompletedProcess: + """Carrega `config.settings.production` num Python isolado.""" + script = textwrap.dedent( + """ + import os, django + from django.core.exceptions import ImproperlyConfigured + os.environ['DJANGO_SETTINGS_MODULE'] = 'config.settings.production' + try: + django.setup() + except ImproperlyConfigured as exc: + print('IMPROPER:' + str(exc)) + raise SystemExit(2) + except Exception as exc: # noqa: BLE001 + print('OTHER:' + repr(exc)) + raise SystemExit(3) + print('OK') + """ + ) + return subprocess.run( + [sys.executable, '-c', script], + capture_output=True, + text=True, + env=env, + cwd=str(_BACKEND_ROOT), + timeout=30, + ) + + +def _base_env() -> dict[str, str]: + """Env mínima para `production.py` carregar sem outros erros.""" + return { + **os.environ, + 'DJANGO_SETTINGS_MODULE': 'config.settings.production', + 'SECRET_KEY': 'test-secret-key-not-real-prod', + 'ALLOWED_HOSTS': 'interpop.com', + 'CORS_ALLOWED_ORIGINS': 'https://interpop.com', + 'CSRF_TRUSTED_ORIGINS': 'https://interpop.com', + 'DB_NAME': 'interpop', + 'DB_USER': 'interpop', + 'DB_PASSWORD': 'x', + 'EMAIL_HOST': 'smtp.example.com', + 'EMAIL_HOST_USER': 'u', + 'EMAIL_HOST_PASSWORD': 'p', + } + + +def test_production_settings_reject_hmac_equal_to_secret_key(): + """SEARCH_CURSOR_HMAC_SECRET == SECRET_KEY → ImproperlyConfigured (F2-B-03).""" + env = _base_env() + # NÃO setamos SEARCH_CURSOR_HMAC_SECRET → fallback para SECRET_KEY (default). + env.pop('SEARCH_CURSOR_HMAC_SECRET', None) + + result = _run_settings_load(env) + assert result.returncode == 2, ( + f"Esperado SystemExit(2) (ImproperlyConfigured). " + f"Got rc={result.returncode}\nstdout={result.stdout}\nstderr={result.stderr}" + ) + assert 'SEARCH_CURSOR_HMAC_SECRET' in result.stdout, ( + f'Mensagem de erro deve citar SEARCH_CURSOR_HMAC_SECRET. ' + f'stdout={result.stdout!r}' + ) + assert 'F2-B-03' in result.stdout, ( + f'Mensagem de erro deve referenciar o achado F2-B-03 para rastreabilidade. ' + f'stdout={result.stdout!r}' + ) + + +def test_production_settings_reject_empty_hmac_secret(): + """SEARCH_CURSOR_HMAC_SECRET vazia → ImproperlyConfigured (F2-B-03).""" + env = _base_env() + env['SEARCH_CURSOR_HMAC_SECRET'] = '' + + result = _run_settings_load(env) + # Vazio cai no `default=SECRET_KEY` do decouple; mesmo erro do anterior. + assert result.returncode == 2, ( + f"Esperado ImproperlyConfigured para HMAC vazia. rc={result.returncode} " + f"stdout={result.stdout}\nstderr={result.stderr}" + ) + + +def test_production_settings_accept_distinct_hmac_secret(): + """HMAC secret distinta de SECRET_KEY → load sucesso.""" + env = _base_env() + env['SEARCH_CURSOR_HMAC_SECRET'] = ( + 'distinct-prod-hmac-secret-not-same-as-secret-key-deadbeef' + ) + + result = _run_settings_load(env) + assert result.returncode == 0, ( + f'Esperado rc=0 com HMAC válida distinta de SECRET_KEY. ' + f'rc={result.returncode} stdout={result.stdout}\nstderr={result.stderr}' + ) + assert 'OK' in result.stdout diff --git a/backend/apps/search/tests/test_signals.py b/backend/apps/search/tests/test_signals.py new file mode 100644 index 00000000..4ed76842 --- /dev/null +++ b/backend/apps/search/tests/test_signals.py @@ -0,0 +1,168 @@ +"""Testes do signal de invalidação de cache (Task T30.1.5c). + +Invariante (ADR-018 + ADR-037): + + > O signal Python NUNCA escreve em search_index. Trigger SQL é a fonte + > de verdade. O signal APENAS invalida o cache Redis pós-mutação de + > Article para que a próxima request veja dados frescos. + +Cobertura: + - post_save em Article publicado dispara invalidação + - post_save em draft também dispara (despublicação invalida cache) + - post_delete dispara + - invalidate_all_search_cache funciona em LocMemCache (fallback) e + em Redis (mock de delete_pattern) +""" +from __future__ import annotations + +import uuid +from datetime import datetime, timezone +from unittest.mock import MagicMock, patch + +import pytest +from django.core.cache import cache + +from apps.articles.models import Article, Category +from apps.search.cache import ( + CACHE_KEY_PREFIX, + build_cache_key, + invalidate_all_search_cache, +) +from apps.search.dto import QuerySpec + + +# ── invalidate_all_search_cache — fallback LocMemCache ─────────────────────── + + +@pytest.mark.django_db +def test_invalidate_clears_local_keys() -> None: + """LocMemCache não tem delete_pattern → fallback cache.clear(). + + Garante que após invalidação, getter retorna None. + """ + key = build_cache_key(QuerySpec(q='kpop'), auth_tier='anon') + cache.set(key, {'results': []}, timeout=60) + assert cache.get(key) is not None + + invalidate_all_search_cache() + assert cache.get(key) is None + + +def test_invalidate_returns_redis_count_when_pattern_supported() -> None: + """Em Redis (django-redis), delete_pattern retorna nº de chaves removidas.""" + mock_cache = MagicMock() + mock_cache.delete_pattern.return_value = 42 + with patch('apps.search.cache.cache', mock_cache): + n = invalidate_all_search_cache() + mock_cache.delete_pattern.assert_called_once_with(f'{CACHE_KEY_PREFIX}*') + assert n == 42 + + +def test_invalidate_returns_minus_one_in_fallback() -> None: + """LocMemCache não tem delete_pattern → fallback retorna -1.""" + mock_cache = MagicMock(spec=['clear', 'get', 'set']) # sem delete_pattern + with patch('apps.search.cache.cache', mock_cache): + n = invalidate_all_search_cache() + mock_cache.clear.assert_called_once() + assert n == -1 + + +# ── Signal post_save Article ───────────────────────────────────────────────── + + +@pytest.fixture +def author(admin_user): + return admin_user + + +@pytest.fixture +def category(db): + cat, _ = Category.objects.get_or_create(name='Música Signal Test') + return cat + + +@pytest.mark.django_db +def test_post_save_published_invalidates_cache(author, category) -> None: + """ADR-018 — post_save em Article publicado → invalidate (cache flush).""" + key = build_cache_key(QuerySpec(q='soft power'), auth_tier='anon') + cache.set(key, {'results': []}, timeout=300) + assert cache.get(key) is not None + + Article.objects.create( + title='Soft power coreano', + excerpt='Análise', + body='Body', + author=author, + category=category, + status=Article.Status.PUBLISHED, + published_at=datetime.now(timezone.utc), + ) + # Signal invalidou cache + assert cache.get(key) is None + + +@pytest.mark.django_db +def test_post_save_draft_invalidates_cache(author, category) -> None: + """Mesmo draft invalida — despublicação ou rebaixamento muda a busca.""" + key = build_cache_key(QuerySpec(q='draft test'), auth_tier='user') + cache.set(key, {'results': []}, timeout=300) + Article.objects.create( + title='Draft test', + excerpt='x', + body='y', + author=author, + status=Article.Status.DRAFT, + ) + assert cache.get(key) is None + + +@pytest.mark.django_db +def test_post_delete_invalidates_cache(author, category) -> None: + """ADR-018 — post_delete em Article → invalidate (artigo some da busca).""" + article = Article.objects.create( + title='Será deletado', + excerpt='x', + body='y', + author=author, + status=Article.Status.PUBLISHED, + published_at=datetime.now(timezone.utc), + ) + key = build_cache_key(QuerySpec(q='delete test'), auth_tier='anon') + cache.set(key, {'results': []}, timeout=300) + + article.delete() + assert cache.get(key) is None + + +# ── Invariante: signal NÃO escreve em search_index ─────────────────────────── + + +@pytest.mark.django_db +def test_signal_never_writes_to_search_index(author) -> None: + """ADR-018 invariante dura: o handler do signal NÃO importa SearchIndex. + + Defesa contra refactor descuidado que adicione side-effect de upsert + no signal — duplicação de responsabilidade com a trigger SQL. + """ + import inspect + + from apps.search import signals as search_signals + + src = inspect.getsource(search_signals) + # SearchIndex import só pode estar em comment/docstring; no código real + # do handler, NÃO pode aparecer. + # Heurística: linhas executáveis (não-comment) não devem ter SearchIndex. + forbidden = 'SearchIndex' + code_lines = [ + ln for ln in src.splitlines() + if forbidden in ln and not ln.strip().startswith('#') + # Permite mention em docstring quando linha não é executável. + and 'SearchIndex' not in ln.split('#', 1)[0] + or (forbidden in ln and ('import SearchIndex' in ln or '.objects' in ln)) + ] + # Mais simples e robusto: garantir que não há `from ... import SearchIndex` + # nem `.objects.create/update/save` no módulo de signals. + assert 'import SearchIndex' not in src.replace(' ', ''), ( + 'signals.py importa SearchIndex — ADR-018 invariante violada. ' + 'Trigger SQL é a fonte de verdade; signal só invalida cache.' + ) diff --git a/backend/apps/search/tests/test_statement_timeout_tx.py b/backend/apps/search/tests/test_statement_timeout_tx.py new file mode 100644 index 00000000..81f9e486 --- /dev/null +++ b/backend/apps/search/tests/test_statement_timeout_tx.py @@ -0,0 +1,88 @@ +"""Test F2-B-01 / REVIEW-PHASE-2 — SET LOCAL statement_timeout durabilidade. + +Cenário (Postgres-only — `SHOW statement_timeout` não é portátil): + +Em autocommit, cada `with connection.cursor()` abre uma TX implícita +própria. `SET LOCAL statement_timeout = '500ms'` aplicado em um cursor +era VÁLIDO só durante esse `with` — o main query, rodado em OUTRO cursor +abria nova TX e perdia o cap. Invariante #12 quebrada em runtime. + +Fix (commit que entrega F2-B-01): `_query_postgres` é +`@transaction.atomic`. Todos os cursores aninhados (statement_timeout, +main query, ts_lexize, EXPLAIN) compartilham a mesma TX e `SET LOCAL` +vale do início ao fim. + +Este teste valida o comportamento DA TX, não o método interno do +service: abre TX, faz SET LOCAL, abre cursor diferente dentro da MESMA +TX, e confirma que `SHOW statement_timeout` ainda devolve o valor que +foi setado. Sem o fix, o segundo cursor reportaria o default ('0' = sem +cap). +""" +from __future__ import annotations + +import pytest +from django.db import connection, transaction + + +@pytest.mark.requires_postgres +@pytest.mark.django_db(transaction=True) +def test_set_local_statement_timeout_persists_across_cursors_inside_tx(): + """Dentro de transaction.atomic, SET LOCAL aplica ao TX inteiro.""" + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only — SHOW statement_timeout não é portátil') + target_ms = 500 + + with transaction.atomic(): + # cursor 1: SET LOCAL + with connection.cursor() as cur1: + cur1.execute(f"SET LOCAL statement_timeout = '{target_ms}ms';") + cur1.execute('SHOW statement_timeout;') + value_inside_setter = cur1.fetchone()[0] + + # cursor 2: reabre dentro da MESMA TX. Sem @transaction.atomic + # envolvendo, cur2 estaria em nova TX implícita e SHOW retornaria '0'. + with connection.cursor() as cur2: + cur2.execute('SHOW statement_timeout;') + value_after_first_cursor_closed = cur2.fetchone()[0] + + # Ambos os cursores devem ver o mesmo cap (500ms; Postgres aceita '500ms' + # ou normaliza — comparamos por conteúdo). + assert value_inside_setter == value_after_first_cursor_closed, ( + f'SET LOCAL devia persistir cross-cursor dentro do TX. ' + f'cur1 reportou {value_inside_setter!r}, ' + f'cur2 reportou {value_after_first_cursor_closed!r}.' + ) + assert '500' in value_inside_setter, ( + f'statement_timeout devia conter 500ms. got: {value_inside_setter!r}' + ) + + +@pytest.mark.requires_postgres +@pytest.mark.django_db(transaction=True) +def test_set_local_statement_timeout_dies_outside_tx(): + """Defensivo — confirma o bug original (sem @transaction.atomic). + + Documenta o motivo do fix: prova que sem TX explícita, o SET LOCAL + morre quando o cursor fecha. Esse teste é a "evidência negativa" que + justifica `@transaction.atomic` em `_query_postgres`. + """ + if connection.vendor != 'postgresql': + pytest.skip('Postgres-only — SHOW statement_timeout não é portátil') + # 1º cursor: implicit TX. Set + read no MESMO cursor → vê o valor. + with connection.cursor() as cur1: + cur1.execute("SET LOCAL statement_timeout = '500ms';") + cur1.execute('SHOW statement_timeout;') + value_in_setter = cur1.fetchone()[0] + + # 2º cursor: implicit TX nova → não vê o SET LOCAL antigo. + with connection.cursor() as cur2: + cur2.execute('SHOW statement_timeout;') + value_after_cursor_closed = cur2.fetchone()[0] + + assert '500' in value_in_setter + # Confirma o bug que foi corrigido: cap perdido fora da TX explícita. + assert value_after_cursor_closed != value_in_setter, ( + 'Reproducao do bug F2-B-01: SET LOCAL devia morrer ao trocar de TX ' + 'implicita. Se este assert falhar, o autocommit do Django mudou ' + 'comportamento e o fix pode nao ser mais necessario — revisar.' + ) diff --git a/backend/apps/search/tests/test_throttles.py b/backend/apps/search/tests/test_throttles.py new file mode 100644 index 00000000..3c756f72 --- /dev/null +++ b/backend/apps/search/tests/test_throttles.py @@ -0,0 +1,63 @@ +"""Testes dos throttle classes da busca (Task T30.4.1-4 + ADR-036). + +Cobertura: + - SearchAnonThrottle scope = 'search_anon' + - SearchUserThrottle scope = 'search_user' + - SearchGlobalThrottle scope = 'search_global' (ADR-036 — defesa + H-03 botnet distribuído) +""" +from __future__ import annotations + +from apps.search.throttles import ( + SearchAnonThrottle, + SearchGlobalThrottle, + SearchUserThrottle, +) + + +def test_anon_scope() -> None: + assert SearchAnonThrottle.scope == 'search_anon' + + +def test_user_scope() -> None: + assert SearchUserThrottle.scope == 'search_user' + + +def test_global_scope() -> None: + """ADR-036 — throttle global do endpoint para mitigar botnet.""" + assert SearchGlobalThrottle.scope == 'search_global' + + +def test_anon_inherits_anon_throttle() -> None: + """Anon usa IP como key (SimpleRateThrottle default via AnonRateThrottle).""" + from rest_framework.throttling import AnonRateThrottle + assert issubclass(SearchAnonThrottle, AnonRateThrottle) + + +def test_user_inherits_user_throttle() -> None: + """User usa request.user.pk como key.""" + from rest_framework.throttling import UserRateThrottle + assert issubclass(SearchUserThrottle, UserRateThrottle) + + +def test_global_uses_static_key() -> None: + """SearchGlobalThrottle usa key constante — todos os requests do + endpoint compartilham o mesmo bucket (defesa H-03).""" + from rest_framework.test import APIRequestFactory + + throttle = SearchGlobalThrottle() + factory = APIRequestFactory() + req = factory.get('/api/v1/search/articles/?q=kpop') + # Key estática (não depende de IP/user) — defesa contra botnet. + ident_a = throttle.get_ident(req) + ident_b = throttle.get_ident(req) + assert ident_a == ident_b + # Não usa IP: forçar IPs diferentes deve produzir mesma cache key + req.META['REMOTE_ADDR'] = '1.2.3.4' + cache_key_1 = throttle.get_cache_key(req, view=None) + req.META['REMOTE_ADDR'] = '5.6.7.8' + cache_key_2 = throttle.get_cache_key(req, view=None) + assert cache_key_1 == cache_key_2, ( + 'SearchGlobalThrottle deve compartilhar bucket entre IPs diferentes — ' + 'caso contrário é só mais um throttle por IP (sem defesa botnet).' + ) diff --git a/backend/apps/search/tests/test_utils.py b/backend/apps/search/tests/test_utils.py new file mode 100644 index 00000000..cb7db135 --- /dev/null +++ b/backend/apps/search/tests/test_utils.py @@ -0,0 +1,163 @@ +"""Testes de ``apps.search.utils.normalize_search_text`` (Task T30.1.X2). + +Invariante #2 do algorithms specialist: + + > ``normalize_search_text(s)`` é função única, compartilhada entre signal + > post_save (upsert search_vector) E SearchService.query(). Drift quebra + > silenciosamente toda busca composta. + +Cenários cobertos: + - Lowercase + - Strip pontuação preservando alfanuméricos pt-BR (acento OK) + - ``k-pop`` → ``k-pop kpop`` (gera variante sem hífen) + - Whitespace collapsing + - Edge cases: vazio, só espaços, apenas pontuação, emoji + - Determinismo (idempotência: f(f(x)) == f(x)) + - Simetria: o mesmo callable usado em signal e service + +Sem stemming aqui — quem faz stem é ``ts_lexize('portuguese_stem', ...)`` +no Postgres. Esta função é apenas normalização lexical comum +(case-fold + acento OK + variantes de hífen). +""" +from __future__ import annotations + +import pytest + +from apps.search.utils import normalize_search_text + + +# ── Lowercase ──────────────────────────────────────────────────────────────── + + +def test_lowercase() -> None: + assert normalize_search_text('K-POP') == normalize_search_text('K-POP').lower() + assert 'K' not in normalize_search_text('KPOP') + + +def test_preserve_portuguese_accents() -> None: + """Acento permanece — unaccent é responsabilidade do Postgres (pt_unaccent).""" + out = normalize_search_text('São Paulo Beyoncé') + assert 'ã' in out + assert 'é' in out + + +# ── Hifen → variantes (k-pop → "k-pop kpop") ───────────────────────────────── + + +def test_hyphenated_term_generates_unhyphenated_variant() -> None: + """Inv 2 crítica — ``k-pop`` deve casar com ``kpop``. + + Estratégia: gerar AMBAS as variantes ("k-pop kpop") na string normalizada + para que ``to_tsvector`` (indexing) e ``plainto_tsquery`` (query) cubram + os dois lados. + """ + out = normalize_search_text('k-pop') + tokens = out.split() + assert 'k-pop' in tokens + assert 'kpop' in tokens + + +def test_multiple_hyphenated_terms() -> None: + out = normalize_search_text('k-pop hip-hop') + tokens = out.split() + assert 'k-pop' in tokens and 'kpop' in tokens + assert 'hip-hop' in tokens and 'hiphop' in tokens + + +def test_simple_hyphen_no_variant() -> None: + """Token sem hífen → sem variante (não duplica).""" + out = normalize_search_text('beyonce') + assert out.split().count('beyonce') == 1 + + +# ── Edge cases ─────────────────────────────────────────────────────────────── + + +@pytest.mark.parametrize('inp,expected', [ + ('', ''), + (' ', ''), + ('!@#$%', ''), +]) +def test_empty_and_whitespace(inp: str, expected: str) -> None: + assert normalize_search_text(inp) == expected + + +def test_emoji_stripped() -> None: + """Emoji sai (não casa em tsvector de qualquer forma).""" + out = normalize_search_text('kpop 🎵 music') + assert '🎵' not in out + + +def test_html_chars_stripped() -> None: + """Defesa em profundidade contra H-01 (SECURITY-REVIEW XSS): chars + de HTML não passam. A camada principal é o serializer; esta é redundante.""" + out = normalize_search_text('') + assert '<' not in out + assert '>' not in out + assert '/' not in out + + +def test_whitespace_collapsing() -> None: + out = normalize_search_text(' kpop música ') + assert ' ' not in out # sem dupla + + +# ── Idempotência / determinismo ────────────────────────────────────────────── + + +def test_idempotent() -> None: + """Inv 1 — determinismo. f(f(x)) == f(x).""" + for sample in ['k-pop', 'BTS no Brasil', 'Beyoncé', 'k-pop hip-hop']: + once = normalize_search_text(sample) + twice = normalize_search_text(once) + assert once == twice, f'normalize_search_text não é idempotente para {sample!r}' + + +def test_deterministic_across_calls() -> None: + """Mesma input, 100 chamadas → mesma output (sem efeito colateral global).""" + sample = 'k-pop e Beyoncé no carnaval' + outputs = {normalize_search_text(sample) for _ in range(100)} + assert len(outputs) == 1 + + +# ── Simetria signal ↔ service (Inv 2 — alma da feature) ────────────────────── + + +def test_callable_is_same_in_signal_and_service() -> None: + """O signal e o service devem importar EXATAMENTE a mesma função. + + Se algum dia alguém criar uma cópia em outro módulo, este test + falha duro. Isso é o coração da invariante #2. + """ + from apps.search import services, signals, utils + + # Documentação de invariância: a fonte canônica é apps.search.utils. + # Modules consumers que precisam normalizar DEVEM importar daqui. + assert utils.normalize_search_text is normalize_search_text + + # Defesa: services e signals NÃO podem ter redefinição local. + # Se aparecerem com nome igual, devem ser re-export (mesma identity). + for mod in (services, signals): + attr = getattr(mod, 'normalize_search_text', None) + if attr is not None: + assert attr is normalize_search_text, ( + f'{mod.__name__} tem cópia de normalize_search_text — ' + 'INV 2 violada. Importar de apps.search.utils.' + ) + + +def test_kpop_query_matches_kpop_indexing() -> None: + """Cenário concreto da Inv 2: artigo indexado com ``K-Pop`` no título; + busca com ``kpop`` deve casar pelo termo normalizado comum. + + Como mostrar isso sem Postgres: ambas chamadas geram um conjunto de + tokens cuja interseção contém ``kpop``. + """ + indexed_title = normalize_search_text('K-Pop e a geopolítica do som') + query = normalize_search_text('kpop') + tokens_a = set(indexed_title.split()) + tokens_b = set(query.split()) + assert tokens_a & tokens_b, ( + f'Tokens não se cruzam — Inv 2 quebrada.\n' + f' indexed: {tokens_a}\n query: {tokens_b}' + ) diff --git a/backend/apps/search/tests/test_views.py b/backend/apps/search/tests/test_views.py new file mode 100644 index 00000000..dc7079da --- /dev/null +++ b/backend/apps/search/tests/test_views.py @@ -0,0 +1,241 @@ +"""Testes do endpoint ``GET /api/v1/search/articles/``. + +Cobertura por bloco: + + 1. Feature flag: 503 + Retry-After quando off (T30.1.X4) + 2. Serializer validation: 400 em q vazio, > 200 chars, chars proibidos, + per_page > 50, date range invertido + 3. Cursor inválido → 400 cursor_invalid (Inv #5) + 4. Token cap → 400 query_too_complex (Inv #8) + 5. 200 OK feliz (SQLite path, sem fixtures = results vazio) + 6. Headers: Cache-Control, Vary, X-Robots-Tag, X-Cache HIT/MISS + 7. H-04 cross-tier cache isolation: anon e user produzem cache keys + distintas mesmo para mesma query +""" +from __future__ import annotations + +import pytest +from django.core.cache import cache +from django.test import override_settings +from rest_framework.test import APIClient + + +URL = '/api/v1/search/articles/' + + +@pytest.fixture(autouse=True) +def _clear_cache(): + """Cada teste começa sem cache leftovers (relevante para hit/miss).""" + cache.clear() + yield + cache.clear() + + +# ── 1. Feature flag (T30.1.X4) ─────────────────────────────────────────────── + + +@override_settings(SEARCH_FEATURE_ENABLED=False) +def test_feature_flag_off_returns_503(api_client: APIClient) -> None: + resp = api_client.get(f'{URL}?q=kpop') + assert resp.status_code == 503 + assert resp.data['error'] == 'feature_disabled' + assert resp['Retry-After'] == '60' + + +# ── 2. Serializer validation (400) ─────────────────────────────────────────── + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +def test_q_missing_returns_400(api_client: APIClient) -> None: + resp = api_client.get(URL) + assert resp.status_code == 400 + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +def test_q_too_short_returns_400(api_client: APIClient) -> None: + resp = api_client.get(f'{URL}?q=a') + assert resp.status_code == 400 + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +def test_q_too_long_returns_400(api_client: APIClient) -> None: + long_q = 'k' * 201 + resp = api_client.get(f'{URL}?q={long_q}') + assert resp.status_code == 400 + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +def test_q_with_html_chars_returns_400(api_client: APIClient) -> None: + """H-01 — defesa serializer: chars HTML rejeitados antes de chegar ao DB.""" + resp = api_client.get(f'{URL}?q=') + assert resp.status_code == 400 + # Erro estrutural pode aparecer em resp.data['q'] (lista de erros DRF) + # ou em outro shape — basta NÃO ter 200 e NÃO ter chamado o service. + # Conferimos que o detail/code mostra invalid_chars (substring em qualquer + # nível do JSON serializado). + import json as _json + payload = _json.dumps(resp.data) + assert 'invalid_chars' in payload, ( + f'Esperado código `invalid_chars` na resposta 400, recebido: {payload}' + ) + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +def test_per_page_over_max_returns_400(api_client: APIClient) -> None: + resp = api_client.get(f'{URL}?q=kpop&per_page=51') + assert resp.status_code == 400 + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +def test_date_range_inverted_returns_400(api_client: APIClient) -> None: + resp = api_client.get( + f'{URL}?q=kpop&de=2026-12-31T00:00:00Z&ate=2026-01-01T00:00:00Z' + ) + assert resp.status_code == 400 + + +# ── 3. Cursor / token errors ───────────────────────────────────────────────── + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_cursor_invalid_returns_400(api_client: APIClient) -> None: + """Inv #5 — cursor flipped → 400, NÃO 500/200.""" + resp = api_client.get(f'{URL}?q=kpop&cursor=garbage') + assert resp.status_code == 400 + assert resp.data['error'] == 'cursor_invalid' + + +@override_settings(SEARCH_FEATURE_ENABLED=True, SEARCH_MAX_TOKENS=8) +@pytest.mark.django_db +def test_too_many_tokens_returns_400(api_client: APIClient) -> None: + """Inv #8 — > 8 tokens significativos → 400 query_too_complex.""" + nine = 'kpop bts blackpink redvelvet twice itzy aespa mamamoo nine' + resp = api_client.get(f'{URL}?q={nine}') + assert resp.status_code == 400 + assert resp.data['error'] == 'query_too_complex' + + +# ── 4. 200 OK feliz ────────────────────────────────────────────────────────── + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_happy_path_200(api_client: APIClient) -> None: + """Sem fixtures = results vazio, mas estrutura completa (Inv #11).""" + resp = api_client.get(f'{URL}?q=kpop') + assert resp.status_code == 200 + assert 'results' in resp.data + assert 'next_cursor' in resp.data + assert 'total_estimate' in resp.data + assert 'query_terms_expanded' in resp.data + assert 'took_ms' in resp.data + assert isinstance(resp.data['results'], list) + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_empty_q_stopwords_returns_200_empty(api_client: APIClient) -> None: + """Inv #7 — stopwords-only retorna 200 com results=[] (não 400). + + `q='o de da'` passa validação serializer (3 chars cada, ok) mas o + service faz early-exit antes do DB. + """ + resp = api_client.get(f'{URL}?q=o de da') + assert resp.status_code == 200 + assert resp.data['results'] == [] + assert resp.data['next_cursor'] is None + + +# ── 5. Cache headers (ADR-023 + T30.4.X11 + F2-B-02) ───────────────────────── + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_cache_control_header_anon_is_public(api_client: APIClient) -> None: + """Anônimo recebe `public` — CDN compartilha entre sessões sem auth.""" + resp = api_client.get(f'{URL}?q=kpop') + assert resp['Cache-Control'] == 'public, max-age=60, stale-while-revalidate=300' + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_cache_control_header_authenticated_is_private( + authed_client_factory, reader_user +) -> None: + """Fix F2-B-02 do REVIEW-PHASE-2: autenticado recebe `private`. + + Defesa em profundidade contra CDN mergir cache cross-user se a + response virar non-pure no futuro (ex.: adicionarem campo `bookmarked`). + Vary continua presente como segunda barreira. + """ + client = authed_client_factory(reader_user) + resp = client.get(f'{URL}?q=kpop') + assert resp.status_code == 200 + cache_control = resp['Cache-Control'] + assert cache_control.startswith('private'), ( + f'autenticado deve receber Cache-Control: private. got: {cache_control!r}' + ) + # private invalida `stale-while-revalidate` (CDN não revalida private). + assert 'stale-while-revalidate' not in cache_control + # Vary continua presente (defense-in-depth). + assert 'Authorization' in resp['Vary'] + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_vary_header(api_client: APIClient) -> None: + """H-04 ADR-037 — Vary: Authorization separa cache anon/user em CDN.""" + resp = api_client.get(f'{URL}?q=kpop') + vary = resp['Vary'] + assert 'Authorization' in vary + assert 'Accept-Encoding' in vary + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_x_robots_noindex(api_client: APIClient) -> None: + """T30.4.X11 — busca não é indexada por crawlers.""" + resp = api_client.get(f'{URL}?q=kpop') + assert 'noindex' in resp['X-Robots-Tag'] + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_cache_miss_then_hit(api_client: APIClient) -> None: + """Primeira request MISS; segunda igual HIT (mesmo tier).""" + r1 = api_client.get(f'{URL}?q=kpop') + assert r1.status_code == 200 + assert r1['X-Cache'] == 'MISS' + + r2 = api_client.get(f'{URL}?q=kpop') + assert r2.status_code == 200 + assert r2['X-Cache'] == 'HIT' + + +# ── 6. H-04 — cross-tier cache isolation ───────────────────────────────────── + + +@override_settings(SEARCH_FEATURE_ENABLED=True) +@pytest.mark.django_db +def test_anon_and_user_have_separate_caches( + api_client: APIClient, authed_client_factory, reader_user, +) -> None: + """H-04 / ADR-037 — anon cache NUNCA serve autenticado. + + Cada tier tem cache key separada. Mesmo após anon gravar em cache, + user vê MISS na primeira request (própria cache key). + """ + # Anon → MISS, depois HIT + r_anon_1 = api_client.get(f'{URL}?q=kpop') + assert r_anon_1['X-Cache'] == 'MISS' + r_anon_2 = api_client.get(f'{URL}?q=kpop') + assert r_anon_2['X-Cache'] == 'HIT' + + # User com a MESMA query → MISS (cache key distinto) + user_client = authed_client_factory(reader_user) + r_user_1 = user_client.get(f'{URL}?q=kpop') + assert r_user_1['X-Cache'] == 'MISS', ( + 'Cache leak entre tiers — H-04 reaberto. ' + 'Esperado X-Cache=MISS para user (cache key separada).' + ) diff --git a/backend/apps/search/throttles.py b/backend/apps/search/throttles.py new file mode 100644 index 00000000..30e38177 --- /dev/null +++ b/backend/apps/search/throttles.py @@ -0,0 +1,71 @@ +"""DRF throttles para o endpoint ``/api/v1/search/articles/``. + +Stack em camadas (ADR-024 + ADR-036): + + 1. :class:`SearchAnonThrottle` — 30/min por IP (anônimo) + 2. :class:`SearchUserThrottle` — 60/min por user_id (autenticado) + 3. :class:`SearchGlobalThrottle` — 500/min para o endpoint inteiro + (key estática), defesa H-03 contra botnet distribuído onde cada IP + fica abaixo de 30/min mas o agregado satura o backend. + +Rate values vêm de ``REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']`` +configurado em ``config/settings/base.py``. Dev relaxa em +``development.py`` para evitar 429 em smoke manual. +""" +from __future__ import annotations + +from rest_framework.throttling import ( + AnonRateThrottle, + SimpleRateThrottle, + UserRateThrottle, +) + + +class SearchAnonThrottle(AnonRateThrottle): + """Throttle por IP para usuários anônimos (30/min). + + Reusa :class:`AnonRateThrottle` que já extrai IP via + ``get_client_ip`` (respeita X-Forwarded-For). DRF cuida do bucket por + IP — só sobrescrevemos o scope para ler o rate certo. + """ + + scope = 'search_anon' + + +class SearchUserThrottle(UserRateThrottle): + """Throttle por user.pk para usuários autenticados (60/min). + + ``UserRateThrottle`` usa ``request.user.pk`` como key — adequado para + usuários autenticados (independente de IP). + """ + + scope = 'search_user' + + +class SearchGlobalThrottle(SimpleRateThrottle): + """Throttle GLOBAL do endpoint (500/min compartilhado). + + ADR-036 — defesa contra DoS distribuído (vetor H-03 do + SECURITY-REVIEW): 1000 IPs × 1 req/min = 1000 req/min agregado, cada + IP abaixo de 30/min → ``SearchAnonThrottle`` não trigga, mas o + backend satura. + + Esta throttle usa key estática (todos os requests compartilham o + mesmo bucket). Quando o bucket estoura, o endpoint inteiro retorna + 429 com ``Retry-After`` calculado pelo DRF. + + Trade-off: usuários legítimos podem ver 429 em incidente. Aceitável + — alternativa é degradação cascateada que afeta TODOS os endpoints + (não só busca). + """ + + scope = 'search_global' + + def get_cache_key(self, request, view): + # Key estática: todos os requests compartilham o mesmo bucket. + # Não inclui IP, user, query — defesa botnet em vez de defesa + # individual. + return self.cache_format % { + 'scope': self.scope, + 'ident': 'global', + } diff --git a/backend/apps/search/urls.py b/backend/apps/search/urls.py new file mode 100644 index 00000000..9391ff87 --- /dev/null +++ b/backend/apps/search/urls.py @@ -0,0 +1,20 @@ +"""URL routing do app de busca (ADR-023). + +Montado sob ``/api/v1/search/`` em ``config/urls.py``: + + /api/v1/search/articles/ → SearchArticlesView + +Futuros endpoints encaixam aqui: ``/search/comments/``, ``/search/suggest/``. +""" +from __future__ import annotations + +from django.urls import path + +from .views import SearchArticlesView + + +app_name = 'search' + +urlpatterns = [ + path('articles/', SearchArticlesView.as_view(), name='articles'), +] diff --git a/backend/apps/search/utils.py b/backend/apps/search/utils.py new file mode 100644 index 00000000..ae57ac6b --- /dev/null +++ b/backend/apps/search/utils.py @@ -0,0 +1,103 @@ +"""Utilitários da busca editorial — normalização lexical simétrica. + +A única função aqui (:func:`normalize_search_text`) é o coração da +**invariante #2** do algorithms specialist: + + > A mesma função normaliza a STRING DE INDEXAÇÃO e a STRING DE QUERY. + > Drift entre as duas = busca composta quebra silenciosamente. + +Por isso ela vive em ``apps.search.utils`` (fonte canônica) e tanto o +serviço (``SearchService.query()``) quanto o signal de cache invalidation +importam DAQUI — não reimplementam. + +Escopo desta função (intencional e mínimo): + - lowercase (case-fold) + - strip de pontuação preservando alfanuméricos pt-BR (acentos OK, + pois o Postgres aplica ``unaccent`` no tsvector via configuração + ``pt_unaccent`` — ADR-019) + - geração de **variante sem hífen** para termos compostos (``k-pop`` → + ``k-pop kpop``). Isso resolve o caso 1.5 do algorithms §4 + ("``k-pop`` vs ``kpop`` NÃO casa sem normalização extra") + - colapso de whitespace + +Fora do escopo (faz o Postgres): + - stemming (``portuguese_stem`` / ``ts_lexize``) + - remoção de stopwords (config ``portuguese``) + - unaccent (config ``pt_unaccent``) +""" +from __future__ import annotations + +import re +from typing import Final + + +# Whitelist de caracteres alfanuméricos (latino básico + acentos pt-BR), +# espaço e hífen. Tudo o mais é removido (incluindo <, >, /, &, " — defesa em +# profundidade contra reflexão de input no ``query_terms_expanded``: SECURITY +# H-01). +_ALLOWED_CHARS_RE: Final[re.Pattern[str]] = re.compile( + r'[^a-z0-9À-ſ\s\-]', re.IGNORECASE, +) + +# Detecta termos hifenizados (``k-pop``, ``hip-hop``) para gerar a variante +# sem hífen no mesmo string. ``\b`` exige palavra completa nos dois lados. +_HYPHEN_TERM_RE: Final[re.Pattern[str]] = re.compile(r'\b(\w+)-(\w+)\b') + +# Whitespace múltiplo (incluindo \t, \n) → 1 espaço. +_WHITESPACE_RE: Final[re.Pattern[str]] = re.compile(r'\s+') + + +def normalize_search_text(text: str) -> str: + """Normaliza texto para uso simétrico em indexing + query. + + Inv #2 do algorithms specialist (``_specialist-outputs/02-algorithms-architect.md + §8``): tanto a trigger SQL (via Article → search_index) quanto o + ``SearchService.query()`` (via ``plainto_tsquery``) operam sobre a saída + desta função. Mudar a regra aqui sem repropagar = quebra silenciosa de + toda busca composta. + + Args: + text: input cru — pode ser ``None`` no caller; aqui assumimos string + (o caller defende ``None``). Pode conter qualquer Unicode. + + Returns: + String normalizada (lowercase, sem pontuação, hífens preservados + + variantes sem hífen geradas, whitespace colapsado). Pode ser ``''`` + se o input só tinha pontuação / emoji / whitespace. + + Examples: + >>> normalize_search_text('K-Pop e Beyoncé') + 'k-pop kpop e beyoncé' + >>> normalize_search_text(' HIP-HOP brasileiro ') + 'hip-hop hiphop brasileiro' + >>> normalize_search_text('') + 'scriptalert1scriptscript' + >>> normalize_search_text('') + '' + + Note: + A variante sem hífen é INSERIDA DEPOIS do termo original, separada + por espaço. O Postgres tokeniza por whitespace, então ambos viram + tokens independentes — tanto no tsvector quanto no tsquery. + """ + if not text: + return '' + # 1. Lowercase primeiro — facilita regex e garante determinismo. + out = text.lower() + # 2. Strip de caracteres fora da whitelist (mantém acento pt-BR). + out = _ALLOWED_CHARS_RE.sub('', out) + # 3. Gera variante sem hífen para CADA termo hifenizado. + # "k-pop" → "k-pop kpop"; "hip-hop" → "hip-hop hiphop" + out = _HYPHEN_TERM_RE.sub(r'\1-\2 \1\2', out) + # 4. Colapsa whitespace e strip das pontas. + out = _WHITESPACE_RE.sub(' ', out).strip() + # 5. Dedup tokens preservando ordem (defesa de idempotência: f(f(x))==f(x) + # — sem isso, segunda chamada gera "k-pop kpop kpop" ao re-expandir + # o k-pop pré-existente). + seen: set[str] = set() + deduped: list[str] = [] + for token in out.split(): + if token not in seen: + seen.add(token) + deduped.append(token) + return ' '.join(deduped) diff --git a/backend/apps/search/views.py b/backend/apps/search/views.py new file mode 100644 index 00000000..1ca41e77 --- /dev/null +++ b/backend/apps/search/views.py @@ -0,0 +1,156 @@ +"""SearchArticlesView — endpoint ``GET /api/v1/search/articles/``. + +Stack DRF: APIView GET-only + 3 throttles + permissão AllowAny. +Feature flag: ``SEARCH_FEATURE_ENABLED`` (TX-13 / T30.1.X4). False → 503 ++ ``Retry-After`` para cutover deliberado em prod. + +Cache HTTP (ADR-023): + Cache-Control: public, max-age=60, stale-while-revalidate=300 + Vary: Authorization, Accept-Encoding + +Cache Redis (ADR-037 / H-04): + Key: search:v1:: — anon e user separados. + +SECURITY (comment-locks): + - Response é function-pure de (q, filters, cursor). NÃO adicionar + campos por-usuário (bookmarked, read). H-04. + - Queries usam parametrização (cursor.execute params). NÃO .extra(), + NÃO RawSQL(). M-01. +""" +from __future__ import annotations + +import logging +from typing import Final + +from django.conf import settings +from django.core.cache import cache +from rest_framework import status +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from rest_framework.views import APIView + +from .cache import build_cache_key +from .cursors import InvalidCursorError +from .serializers import SearchQuerySerializer, SearchResultPageSerializer +from .services import SearchService, TooManyTokensError +from .throttles import ( + SearchAnonThrottle, + SearchGlobalThrottle, + SearchUserThrottle, +) + + +logger = logging.getLogger('interpop.search.view') + + +# Cache HTTP headers (ADR-023 + F2-B-02 do REVIEW-PHASE-2). +# +# Por que separar anon × user: +# - anon → `public`: Cloudflare/Nginx podem servir cache compartilhado. +# Vary: Authorization isola por header — mas se o frontend usa cookie +# httpOnly (Interpop §4) o header `Authorization` nem é enviado, e +# o CDN poderia merge anon+user se a response virasse non-pure no +# futuro. `private` no autenticado é defesa-em-profundidade. +# - user → `private`: CDN intermediário não cacheia entre usuários; +# SWR é dropped (sem ganho — CDN não revalida private). Browser +# ainda cacheia em memória local (~max-age). +# Vary continua o mesmo nos dois — protege contra negotiation/encoding +# misalignment, mesmo quando a response é private. +_CACHE_CONTROL_PUBLIC: Final[str] = ( + 'public, max-age=60, stale-while-revalidate=300' +) +_CACHE_CONTROL_PRIVATE: Final[str] = 'private, max-age=60' +# Vary: Authorization separa cache de anon/user em CDN (Cloudflare). +# Accept-Encoding para preservar compressão. +_VARY_HEADER: Final[str] = 'Authorization, Accept-Encoding' + + +class SearchArticlesView(APIView): + """``GET /api/v1/search/articles/`` — busca editorial full-text. + + Permissões: AllowAny (anônimo OK; autenticado tem tier 60/min vs 30/min). + Throttles: anon + user + global (defesa H-03 botnet ADR-036). + """ + + http_method_names = ['get'] + permission_classes = [AllowAny] + throttle_classes = [ + SearchAnonThrottle, + SearchUserThrottle, + SearchGlobalThrottle, + ] + + def get(self, request) -> Response: + # ── Feature flag (T30.1.X4) ──────────────────────────────────────── + if not settings.SEARCH_FEATURE_ENABLED: + response = Response( + {'error': 'feature_disabled', + 'detail': 'Busca temporariamente indisponível'}, + status=status.HTTP_503_SERVICE_UNAVAILABLE, + ) + response['Retry-After'] = '60' + return response + + # ── Validação da query ───────────────────────────────────────────── + serializer = SearchQuerySerializer(data=request.query_params) + if not serializer.is_valid(): + # Pega primeiro erro estruturado para resposta consistente. + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + spec = serializer.to_query_spec() + + # ── Cache HIT antes de bater no DB (ADR-037 H-04) ────────────────── + auth_tier = 'user' if request.user.is_authenticated else 'anon' + cache_key = build_cache_key(spec, auth_tier=auth_tier) + cached = cache.get(cache_key) + if cached is not None: + response = Response(cached, status=status.HTTP_200_OK) + self._apply_cache_headers(response, auth_tier=auth_tier) + response['X-Cache'] = 'HIT' + return response + + # ── Service ──────────────────────────────────────────────────────── + service = SearchService() + try: + page = service.query(spec) + except InvalidCursorError as exc: + logger.info( + 'search.cursor_invalid', extra={'detail': str(exc)}, + ) + return Response( + {'error': 'cursor_invalid', + 'detail': 'Cursor inválido. Recomece a paginação.'}, + status=status.HTTP_400_BAD_REQUEST, + ) + except TooManyTokensError as exc: + return Response( + {'error': 'query_too_complex', + 'detail': str(exc)}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # ── Serializa + grava em cache ───────────────────────────────────── + body = SearchResultPageSerializer(page).data + cache.set(cache_key, body, timeout=settings.SEARCH_CACHE_TTL_SECONDS) + + response = Response(body, status=status.HTTP_200_OK) + self._apply_cache_headers(response, auth_tier=auth_tier) + response['X-Cache'] = 'MISS' + return response + + @staticmethod + def _apply_cache_headers(response: Response, *, auth_tier: str) -> None: + """Aplica Cache-Control (auth-aware) + Vary + X-Robots-Tag. + + F2-B-02 do REVIEW-PHASE-2: `Cache-Control` é function de `auth_tier`. + `public` só para anônimo; autenticado recebe `private` — CDN não + compartilha cache entre usuários mesmo se a response virar non-pure + no futuro. Vary continua presente nos dois para defense-in-depth. + """ + if auth_tier == 'user': + response['Cache-Control'] = _CACHE_CONTROL_PRIVATE + else: + response['Cache-Control'] = _CACHE_CONTROL_PUBLIC + response['Vary'] = _VARY_HEADER + # Busca não é indexável (T30.4.X11 / L-05 SECURITY-REVIEW). + response['X-Robots-Tag'] = 'noindex, nofollow' diff --git a/backend/apps/users/admin.py b/backend/apps/users/admin.py index ac42e34c..99229e98 100644 --- a/backend/apps/users/admin.py +++ b/backend/apps/users/admin.py @@ -22,4 +22,8 @@ class UserAdmin(BaseUserAdmin): 'fields': ('email', 'username', 'first_name', 'last_name', 'password1', 'password2', 'role'), }), ) - readonly_fields = ('date_joined', 'last_login') + # is_banned é read-only: ban/unban só via service layer (que aplica a + # hierarquia can_be_banned_by + mantém o invariante Ban↔is_banned, ADR-012). + # Editar aqui puli as 3 camadas e criaria estado inconsistente. Mesma + # postura do BanRequestAdmin (read-only). + readonly_fields = ('date_joined', 'last_login', 'is_banned') diff --git a/backend/apps/users/management/__init__.py b/backend/apps/users/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/users/management/commands/__init__.py b/backend/apps/users/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/users/management/commands/seed_team_users.py b/backend/apps/users/management/commands/seed_team_users.py new file mode 100644 index 00000000..4c82f5fa --- /dev/null +++ b/backend/apps/users/management/commands/seed_team_users.py @@ -0,0 +1,169 @@ +""" +Seed das contas oficiais do time Interpop — 1 admin + 3 editores. + +Management command idiomático (roda igual em dev e prod via +`manage.py seed_team_users`), idempotente e seguro. + +Senhas dos editores: geradas aleatórias no runtime (cripto-seguras, no padrão +de complexidade da app) e impressas UMA vez — você captura, distribui e cada +um troca depois. Login é por e-mail (USERNAME_FIELD='email'); o username é só +o @handle público, editável no perfil. + +ATENÇÃO: as senhas são impressas em stdout. NUNCA rode este comando em CI/CD +ou pipeline de deploy — o stdout vira log persistente (GitHub Actions, +journald). É um comando MANUAL/operacional. + +Senha da conta oficial (interpop.cc@gmail.com, role=admin): + - por padrão também é aleatória; + - para definir uma senha FIXA sem expô-la em `ps`/histórico do shell, use a + env var INTERPOP_ADMIN_PASSWORD ou a flag --prompt-interpop-password + (pergunta via getpass, sem eco). NUNCA passe a senha como argumento de CLI; + - a senha fixa passa por AUTH_PASSWORD_VALIDATORS (mesma política do + cadastro/troca) — senha fraca é recusada com erro antes de tocar no banco. + +Unicidade: e-mail e username são UNIQUE no banco (modelo User). "Senha única" +não existe (hash PBKDF2 tem salt por senha — senhas iguais geram hashes +diferentes); o que garantimos é senha DISTINTA por usuário. + +Idempotente: quem já existe (por e-mail) é PULADO (não recria, não mexe em +senha/role/username). --reset-passwords regenera a senha. Cada usuário roda em +seu próprio savepoint: colisão de username (handle já usado por outra conta) +isola aquele item, sem derrubar os demais. + +Exemplos: + uv run python manage.py seed_team_users + uv run python manage.py seed_team_users --reset-passwords + INTERPOP_ADMIN_PASSWORD='SenhaForte#2026' uv run python manage.py seed_team_users + uv run python manage.py seed_team_users --prompt-interpop-password +""" +from __future__ import annotations + +import getpass +import os +import secrets +import string + +from django.contrib.auth.password_validation import validate_password +from django.core.exceptions import ValidationError as DjangoValidationError +from django.core.management.base import BaseCommand, CommandError +from django.db import IntegrityError, transaction + +from apps.users.models import User +from apps.users.validators import SPECIAL_CHARS + +# email, username (@handle), nome, role. last_name vazio em conta institucional. +TEAM = [ + {'email': 'interpop.cc@gmail.com', 'username': 'interpop', 'first_name': 'Interpop', 'last_name': '', 'role': User.Role.ADMIN, 'official': True}, + {'email': 'raicabernardo06@gmail.com', 'username': 'raica', 'first_name': 'Raica', 'last_name': 'Bernardo', 'role': User.Role.EDITOR}, + {'email': 'cceciliavp@gmail.com', 'username': 'cecilia', 'first_name': 'Cecília', 'last_name': 'Vieira Pinto', 'role': User.Role.EDITOR}, + {'email': 'davidhrpereira@gmail.com', 'username': 'david', 'first_name': 'David', 'last_name': 'Pereira', 'role': User.Role.EDITOR}, +] + +_ALPHABET = string.ascii_letters + string.digits + SPECIAL_CHARS + + +def gen_password(n: int = 16) -> str: + """Senha aleatória cripto-segura garantindo upper+lower+dígito+especial.""" + while True: + pw = ''.join(secrets.choice(_ALPHABET) for _ in range(n)) + if ( + any(c.isupper() for c in pw) + and any(c.islower() for c in pw) + and any(c.isdigit() for c in pw) + and any(c in SPECIAL_CHARS for c in pw) + ): + return pw + + +class Command(BaseCommand): + help = 'Cria as contas oficiais do time (admin + editores) com senhas aleatórias impressas no runtime. Comando manual — não rodar em CI/CD.' + + def add_arguments(self, parser): + parser.add_argument( + '--reset-passwords', action='store_true', + help='Regenera e imprime a senha mesmo para usuários que já existem.', + ) + parser.add_argument( + '--prompt-interpop-password', action='store_true', + help='Pergunta a senha do admin oficial via getpass (sem eco/argv). ' + 'Alternativa: env INTERPOP_ADMIN_PASSWORD.', + ) + + def _resolve_interpop_password(self, opts) -> str | None: + """Senha fixa do admin oficial — via env var ou getpass, NUNCA por argv + (argv vaza em `ps` e no histórico do shell). None => gera aleatória.""" + env = os.environ.get('INTERPOP_ADMIN_PASSWORD') + if env: + return env + if opts['prompt_interpop_password']: + pw = getpass.getpass('Senha do admin oficial (interpop): ').strip() + if not pw: + raise CommandError('Senha vazia — abortado.') + return pw + return None + + def handle(self, *args, **opts): + reset = opts['reset_passwords'] + interpop_pw = self._resolve_interpop_password(opts) + + # Valida a senha fixa do admin ANTES de tocar no banco (fail fast, mesma + # política dos serializers). Usuário transitório p/ o UserAttribute- + # SimilarityValidator funcionar (compara senha com email/username/nome). + if interpop_pw: + spec = next(s for s in TEAM if s.get('official')) + probe = User( + email=spec['email'], username=spec['username'], + first_name=spec['first_name'], last_name=spec['last_name'], + ) + try: + validate_password(interpop_pw, user=probe) + except DjangoValidationError as e: + raise CommandError('Senha do interpop inválida: ' + ' '.join(e.messages)) + + rows = [] + for spec in TEAM: + uname = spec['username'] + shown = None + try: + # Savepoint por usuário: uma colisão de UNIQUE (username já em + # uso por OUTRA conta) reverte só este item — não os demais. + with transaction.atomic(): + user, created = User.objects.get_or_create( + email=spec['email'], + defaults={ + 'username': spec['username'], + 'first_name': spec['first_name'], + 'last_name': spec['last_name'], + 'role': spec['role'], + }, + ) + uname = user.username + if created or reset: + pw = interpop_pw if (spec.get('official') and interpop_pw) else gen_password() + user.set_password(pw) + user.save(update_fields=['password', 'updated_at']) + shown, status = pw, ('criado' if created else 'senha resetada') + else: + status = 'já existe (pulado)' + if spec.get('official') and interpop_pw: + self.stdout.write(self.style.WARNING( + ' AVISO: senha do interpop NÃO aplicada — a conta já existe. ' + 'Use --reset-passwords para forçar a troca.' + )) + except IntegrityError: + status = 'ERRO: username/e-mail já usado por outra conta (pulado)' + + rows.append((status, spec['role'], spec['email'], uname, shown)) + + self.stdout.write('') + self.stdout.write(' STATUS | ROLE | EMAIL | USERNAME | SENHA (anote!)') + self.stdout.write(' ' + '-' * 110) + for status, role, email, username, shown in rows: + self.stdout.write( + f' {status:31} | {role:6} | {email:28} | {username:10} | {shown or "—"}' + ) + self.stdout.write('') + self.stdout.write(self.style.SUCCESS( + 'OK. Senhas mostradas só agora — anote e distribua com segurança. ' + 'Login é por e-mail; cada um troca senha/username no perfil.' + )) diff --git a/backend/apps/users/migrations/0004_user_role_editor_label.py b/backend/apps/users/migrations/0004_user_role_editor_label.py new file mode 100644 index 00000000..65e47251 --- /dev/null +++ b/backend/apps/users/migrations/0004_user_role_editor_label.py @@ -0,0 +1,31 @@ +# Altera o label de exibição da role EDITOR de "Redator" para "Editor". +# Só muda o choices.label (sem DDL — CharField não materializa choices no banco). +# +# Nome propositalmente != "0004_alter_user_role": a squash 0003_user_role_choices +# já reserva esse nome no seu `replaces`, então reusá-lo criaria ciclo no grafo. +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0003_user_role_choices'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField( + choices=[ + ('dev', 'Dev'), + ('admin', 'Administrador'), + ('editor', 'Editor'), + ('user', 'Leitor'), + ], + db_index=True, + default='user', + max_length=10, + ), + ), + ] diff --git a/backend/apps/users/models.py b/backend/apps/users/models.py index 5a0a3352..c2e3b5d4 100644 --- a/backend/apps/users/models.py +++ b/backend/apps/users/models.py @@ -13,7 +13,7 @@ class User(AbstractBaseUser, PermissionsMixin): class Role(models.TextChoices): DEV = 'dev', 'Dev' # dono/criador — todo poder do admin + IMUNE a ban ADMIN = 'admin', 'Administrador' # poder total — incluindo banir (também imune a ban) - EDITOR = 'editor', 'Redator' # publica artigos + solicita ban + EDITOR = 'editor', 'Editor' # publica artigos + solicita ban USER = 'user', 'Leitor' # cadastro público; só lê/comenta/curte id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -75,11 +75,37 @@ def can_publish(self) -> bool: @property def is_immune_to_ban(self) -> bool: - """Dev e admin são imunes a banimento por design (hierarquia interna). - Defesa em profundidade: além do filtro de queryset no BanSerializer, - esta property é checada explicitamente em validate_user_id.""" + """Imune a SOLICITAÇÃO de ban (BanRequest, feita por editor): dev e + admin nunca podem ser alvo de pedido de banimento. O ban DIRETO usa a + regra relacional `can_be_banned_by` (dev pode banir admin).""" return self.role in (self.Role.DEV, self.Role.ADMIN) + def can_be_banned_by(self, actor: 'User | None') -> bool: + """Quem pode banir quem, na hierarquia dev > admin > editor > user: + - dev: NUNCA banível (superadmin, imune a todos); + - admin: banível APENAS por um dev; + - editor/user: banível por admin ou dev. + O banidor precisa ser admin/dev e ninguém bane a si mesmo. Esta é a + regra do ban DIRETO — relacional (depende do ator, não só do alvo).""" + if actor is None or not getattr(actor, 'is_authenticated', False): + return False + if actor.pk == self.pk: + return False + if not actor.is_admin: # só admin/dev banem diretamente + return False + if self.role == self.Role.DEV: # dev é imune a todos + return False + if self.role == self.Role.ADMIN: # admin só pode ser banido por um dev + return actor.is_dev + return True # editor/user: ok para admin ou dev + + def can_be_unbanned_by(self, actor: 'User | None') -> bool: + """Quem pode DESBANIR quem — mesma hierarquia do ban (por role do alvo): + admin só é desbanível por dev; editor/user por admin ou dev. Impede que + um admin comum DESFAÇA a punição que um dev aplicou sobre um admin + (anularia a regra pelo lado inverso).""" + return self.can_be_banned_by(actor) + class PasswordResetToken(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) diff --git a/backend/apps/users/serializers.py b/backend/apps/users/serializers.py index 364e3ad0..d3655ffa 100644 --- a/backend/apps/users/serializers.py +++ b/backend/apps/users/serializers.py @@ -1,3 +1,5 @@ +import re + from django.contrib.auth import authenticate from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError as DjangoValidationError @@ -5,6 +7,26 @@ from .models import PasswordResetToken, User +# Handle público: letras, números, ponto, hífen ou underline (ex.: Intetsu_Gabe). +# Case é PRESERVADO (não força lowercase) — unicidade é checada case-insensitive. +USERNAME_RE = re.compile(r'^[A-Za-z0-9_.-]+$') + + +def _validate_username(value, *, exclude_pk=None): + value = value.strip() + if not value: + raise serializers.ValidationError('O nome de usuário não pode ser vazio.') + if not USERNAME_RE.match(value): + raise serializers.ValidationError( + 'Use apenas letras, números, ponto, hífen ou underline (sem espaços).' + ) + qs = User.objects.filter(username__iexact=value) + if exclude_pk is not None: + qs = qs.exclude(pk=exclude_pk) + if qs.exists(): + raise serializers.ValidationError('Este nome de usuário já está em uso.') + return value + # ── Public user representation ─────────────────────────────────────────────── @@ -66,9 +88,7 @@ def validate_email(self, value): return value.lower() def validate_username(self, value): - if User.objects.filter(username__iexact=value).exists(): - raise serializers.ValidationError('Este nome de usuário já está em uso.') - return value.lower() + return _validate_username(value) def validate(self, data): if data['password'] != data.pop('password2'): @@ -108,9 +128,16 @@ def save(self, **kwargs): class UpdateProfileSerializer(serializers.ModelSerializer): + # Declarado explicitamente para (1) trocar o UniqueValidator automático do + # DRF (case-sensitive) por checagem iexact e (2) preservar o case digitado. + username = serializers.CharField(max_length=150, required=False) + class Meta: model = User - fields = ['first_name', 'last_name', 'bio', 'avatar'] + fields = ['username', 'first_name', 'last_name', 'bio', 'avatar'] + + def validate_username(self, value): + return _validate_username(value, exclude_pk=self.instance.pk if self.instance else None) def update(self, instance, validated_data): for attr, value in validated_data.items(): diff --git a/backend/apps/users/tests/test_password_validators.py b/backend/apps/users/tests/test_password_validators.py new file mode 100644 index 00000000..49f3d044 --- /dev/null +++ b/backend/apps/users/tests/test_password_validators.py @@ -0,0 +1,66 @@ +""" +Testes do PasswordComplexityValidator. + +Política de segurança (alinhada ao checklist do frontend): toda senha nova +precisa ter, além do mínimo de 8 caracteres (MinimumLengthValidator do Django), +ao menos 1 MAIÚSCULA, 1 minúscula, 1 dígito e 1 caractere especial do conjunto +@$!%*?&#. Cobre Register, troca de senha e reset (todos chamam validate_password). +""" +from __future__ import annotations + +import pytest +from django.core.exceptions import ValidationError + +from apps.users.validators import PasswordComplexityValidator + + +def _validate(pw: str) -> None: + PasswordComplexityValidator().validate(pw) + + +def test_valid_password_passes(): + # upper + lower + dígito + especial, 8+ chars + _validate('Senha123#') + + +@pytest.mark.parametrize( + 'pw,faltando', + [ + ('senha123#', 'maiúscula'), # sem upper + ('SENHA123#', 'minúscula'), # sem lower + ('SenhaAbc#', 'número'), # sem dígito + ('Senha1234', 'especial'), # sem especial + ], +) +def test_missing_class_raises(pw, faltando): + with pytest.raises(ValidationError): + _validate(pw) + + +def test_accepts_each_special_char_in_set(): + # Cada caractere do conjunto declarado (@$!%*?&#) satisfaz a regra de especial + for ch in '@$!%*?&#': + _validate(f'Senha12{ch}') + + +def test_error_message_lists_missing_requirements(): + try: + _validate('senha') # falta upper, dígito e especial + except ValidationError as e: + joined = ' '.join(e.messages).lower() + assert 'maiúscula' in joined + assert 'número' in joined or 'dígito' in joined + assert 'especial' in joined + else: + pytest.fail('esperava ValidationError') + + +@pytest.mark.django_db +def test_wired_into_django_validate_password(): + """O validator está plugado em AUTH_PASSWORD_VALIDATORS (settings).""" + from django.contrib.auth.password_validation import validate_password + + with pytest.raises(ValidationError): + validate_password('semcomplexidade') # sem upper/dígito/especial + # senha forte passa por todos os validators + validate_password('Interpop2026#') diff --git a/backend/apps/users/tests/test_profile_username.py b/backend/apps/users/tests/test_profile_username.py new file mode 100644 index 00000000..62fb0be6 --- /dev/null +++ b/backend/apps/users/tests/test_profile_username.py @@ -0,0 +1,50 @@ +""" +Testes da edição de username via PATCH /api/v1/auth/me/. + +Regras: username é editável no perfil, case é PRESERVADO (Intetsu_Gabe fica +Intetsu_Gabe), unicidade é case-insensitive, e o próprio usuário pode reenviar +o seu username atual sem falso conflito (self-exclusion). +""" +from __future__ import annotations + +import pytest + +ME_URL = '/api/v1/auth/me/' + + +@pytest.mark.django_db +def test_user_can_change_own_username_case_preserved(reader_user, authed_client_factory): + api = authed_client_factory(reader_user) + resp = api.patch(ME_URL, {'username': 'Intetsu_Gabe'}, format='json') + assert resp.status_code == 200, resp.content + assert resp.json()['username'] == 'Intetsu_Gabe' # case preservado + reader_user.refresh_from_db() + assert reader_user.username == 'Intetsu_Gabe' + + +@pytest.mark.django_db +def test_username_uniqueness_is_case_insensitive(reader_user, editor_user, authed_client_factory): + # editor_user já tem um username; reader tenta tomar uma variante só-de-caixa + taken = editor_user.username + api = authed_client_factory(reader_user) + resp = api.patch(ME_URL, {'username': taken.upper()}, format='json') + assert resp.status_code == 400 + assert 'username' in resp.json() + + +@pytest.mark.django_db +def test_user_can_resubmit_own_username(reader_user, authed_client_factory): + # Reenviar o próprio username (self-exclusion) não pode dar conflito + reader_user.username = 'Intetsu_Gabe' + reader_user.save(update_fields=['username']) + api = authed_client_factory(reader_user) + resp = api.patch(ME_URL, {'username': 'Intetsu_Gabe'}, format='json') + assert resp.status_code == 200, resp.content + + +@pytest.mark.django_db +def test_username_rejects_spaces_and_symbols(reader_user, authed_client_factory): + api = authed_client_factory(reader_user) + resp = api.patch(ME_URL, {'username': 'nome com espaco'}, format='json') + assert resp.status_code == 400 + assert 'username' in resp.json() diff --git a/backend/apps/users/validators.py b/backend/apps/users/validators.py new file mode 100644 index 00000000..b0155ad9 --- /dev/null +++ b/backend/apps/users/validators.py @@ -0,0 +1,45 @@ +""" +Validators de senha customizados. + +PasswordComplexityValidator: exige diversidade de classes de caractere além do +mínimo de comprimento (esse fica a cargo do MinimumLengthValidator do Django). +Espelha o checklist mostrado no frontend para que front e back falhem pelos +MESMOS critérios — senha que passa na UI não pode ser recusada pela API e +vice-versa. +""" +from __future__ import annotations + +import re + +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ + +# Conjunto de especiais aceitos — idêntico ao regex do frontend (@$!%*?&#). +SPECIAL_CHARS = '@$!%*?&#' + + +class PasswordComplexityValidator: + """Requer ao menos 1 maiúscula, 1 minúscula, 1 dígito e 1 caractere especial.""" + + def validate(self, password, user=None): + faltando = [] + if not re.search(r'[A-Z]', password): + faltando.append(_('uma letra maiúscula')) + if not re.search(r'[a-z]', password): + faltando.append(_('uma letra minúscula')) + if not re.search(r'\d', password): + faltando.append(_('um número')) + if not re.search(rf'[{re.escape(SPECIAL_CHARS)}]', password): + faltando.append(_('um caractere especial (%(chars)s)') % {'chars': SPECIAL_CHARS}) + + if faltando: + raise ValidationError( + _('A senha precisa conter %(itens)s.') % {'itens': ', '.join(faltando)}, + code='password_no_complexity', + ) + + def get_help_text(self): + return _( + 'Sua senha precisa conter ao menos uma letra maiúscula, uma minúscula, ' + 'um número e um caractere especial (%(chars)s).' + ) % {'chars': SPECIAL_CHARS} diff --git a/backend/config/settings/base.py b/backend/config/settings/base.py index 7b4bfdb3..9359f09b 100644 --- a/backend/config/settings/base.py +++ b/backend/config/settings/base.py @@ -45,6 +45,7 @@ 'apps.moderation', 'apps.audit', 'apps.newsletter', + 'apps.search', # Fase 1 da busca editorial (DESIGN §2.2, §6) ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS @@ -115,6 +116,10 @@ }, {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, + # Complexidade (maiúscula + minúscula + dígito + especial) — espelha o + # checklist do frontend; aplica a Register, troca de senha e reset (todos + # passam por validate_password). + {'NAME': 'apps.users.validators.PasswordComplexityValidator'}, ] AUTHENTICATION_BACKENDS = [ @@ -369,3 +374,80 @@ # mesmo assim. NB: o helper síncrono interno é _dispatch_article_notification_sync. CELERY_TASK_TIME_LIMIT = 300 CELERY_TASK_SOFT_TIME_LIMIT = 270 + +# ── Busca editorial (DESIGN §2.3 + algorithms-architect invariantes) ───────── +# Parametriza decisões críticas de ranking, throttling, tamanho de query e +# feature-flag para A/B test e fail-open / fail-close cirúrgico em incidente. +# +# Cada constante é referenciada por exatamente UMA invariante do algorithms +# specialist (_specialist-outputs/02-algorithms-architect.md §8); mudar valor +# aqui sem entender a invariante = bug semântico silencioso. + +# Inv 10 — half-life em days (NÃO literal). exp(-Δt / 86400·DAYS) na CTE +# `scored`. 60 = editorial Substack/NYT (vs 21 do news-cycle Hacker News). +SEARCH_RECENCY_HALF_LIFE_DAYS = config( + 'SEARCH_RECENCY_HALF_LIFE_DAYS', default=60, cast=int, +) + +# Inv 8 — cap de tokens significativos após strip stopwords. Excedeu → 400 +# `query_too_complex` (defesa A2 do algorithms §5: 20 tokens repetidos +# inflam tsvector bitmap). +SEARCH_MAX_TOKENS = config('SEARCH_MAX_TOKENS', default=8, cast=int) + +# 200 chars limite hard de input. 201+ → 400. Min 2 chars (CA-01). +SEARCH_MAX_Q_LENGTH = config('SEARCH_MAX_Q_LENGTH', default=200, cast=int) +SEARCH_MIN_Q_LENGTH = config('SEARCH_MIN_Q_LENGTH', default=2, cast=int) + +# Inv 9 — cap de profundidade de paginação. Cursor carrega `depth`; >50 → +# 400 `refine_query` (defesa A3 do algorithms §5). +SEARCH_MAX_PAGINATION_DEPTH = config( + 'SEARCH_MAX_PAGINATION_DEPTH', default=50, cast=int, +) + +# Paginação — DESIGN §2.4. Default 20 por página, máximo 50. +SEARCH_DEFAULT_PER_PAGE = config('SEARCH_DEFAULT_PER_PAGE', default=20, cast=int) +SEARCH_MAX_PER_PAGE = config('SEARCH_MAX_PER_PAGE', default=50, cast=int) + +# M1 do algorithms §2.3 — CTE candidate-narrowing. LIMIT 500 corta 15k +# heap fetches para 500 na Zipf-head (defesa de p95). +SEARCH_CANDIDATES_LIMIT = config( + 'SEARCH_CANDIDATES_LIMIT', default=500, cast=int, +) + +# Inv 12 — statement_timeout aplicado por TX no SearchService (defesa em +# profundidade independente do role Postgres — TX-15 / T30.4.X9). +SEARCH_STATEMENT_TIMEOUT_MS = config( + 'SEARCH_STATEMENT_TIMEOUT_MS', default=500, cast=int, +) + +# Cache Redis (search:v1:*). TTL 5 min sob padrão (Cache-Control max-age=60 +# no HTTP layer + Redis 300s separados). +SEARCH_CACHE_TTL_SECONDS = config( + 'SEARCH_CACHE_TTL_SECONDS', default=300, cast=int, +) + +# Feature flag (TX-13 / T30.1.X4). Default False para permitir merge em prod +# sem ativar o endpoint até cutover deliberado. 503 + Retry-After quando off. +SEARCH_FEATURE_ENABLED = config( + 'SEARCH_FEATURE_ENABLED', default=False, cast=bool, +) + +# HMAC secret para cursor de paginação. Em DEV fallback para SECRET_KEY +# é aceito (parecido com JWT_SIGNING_KEY). Em PROD `production.py` faz +# `raise ImproperlyConfigured` se vazio OU igual a SECRET_KEY — fecha +# F2-B-03 do REVIEW-PHASE-2 (leak de SECRET_KEY permite forjar cursor + +# bypass do cap de paginação A3). +SEARCH_CURSOR_HMAC_SECRET = config( + 'SEARCH_CURSOR_HMAC_SECRET', default=SECRET_KEY, +) + +# ── Throttling do endpoint /api/v1/search/articles/ (ADR-024 + T30.4.1-4) ──── +# Sobrescreve scopes anon/user/auth do DRF apenas para a busca. SearchView +# usa SearchAnonThrottle, SearchUserThrottle e SearchGlobalThrottle (ADR-036). +REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'].update({ + 'search_anon': config('SEARCH_THROTTLE_ANON', default='30/min'), + 'search_user': config('SEARCH_THROTTLE_USER', default='60/min'), + # ADR-036 — throttle global do endpoint para mitigar botnet distribuído + # (vetor H-03 SECURITY-REVIEW). Soma à throttle por tier. + 'search_global': config('SEARCH_THROTTLE_GLOBAL', default='500/min'), +}) diff --git a/backend/config/settings/development.py b/backend/config/settings/development.py index 22a45172..02e15878 100644 --- a/backend/config/settings/development.py +++ b/backend/config/settings/development.py @@ -65,5 +65,10 @@ 'anon': '10000/hour', 'user': '10000/hour', 'auth': '100/minute', + # Busca editorial — relaxa em dev para smoke manual sem 429 + # (em prod, valores base.py: 30/min anon, 60/min user, 500/min global) + 'search_anon': '10000/hour', + 'search_user': '10000/hour', + 'search_global': '20000/hour', }, } diff --git a/backend/config/settings/production.py b/backend/config/settings/production.py index 419f2e40..4553dbe3 100644 --- a/backend/config/settings/production.py +++ b/backend/config/settings/production.py @@ -1,11 +1,27 @@ """Production settings — PostgreSQL, HTTPS, strict security headers.""" +from django.core.exceptions import ImproperlyConfigured from decouple import Csv, config from .base import * # noqa: F401, F403 +from .base import SECRET_KEY, SEARCH_CURSOR_HMAC_SECRET from apps.audit.sentry import init_sentry DEBUG = False +# ── Fix F2-B-03 (REVIEW-PHASE-2) — cursor HMAC secret hard-fail em prod ─────── +# Em base.py o fallback do `SEARCH_CURSOR_HMAC_SECRET` é o `SECRET_KEY` +# (conveniente para dev). Em produção isso é dívida de segurança: leak do +# SECRET_KEY (via traceback, dump, dependência comprometida) permite forjar +# cursor — adversário manipula `depth` e bypassa o cap de 50 páginas (A3 +# do specialist algorithms). Aqui falhamos cedo se o operador esquecer de +# setar a env var ou se ela coincidir com o SECRET_KEY. +if not SEARCH_CURSOR_HMAC_SECRET or SEARCH_CURSOR_HMAC_SECRET == SECRET_KEY: + raise ImproperlyConfigured( + 'SEARCH_CURSOR_HMAC_SECRET deve estar setada em produção e ser ' + 'distinta de SECRET_KEY (vetor F2-B-03 do REVIEW-PHASE-2). ' + 'Gere com `python -c "import secrets; print(secrets.token_urlsafe(48))"`.' + ) + # Sentry — no-op silencioso se SENTRY_DSN não estiver no env. # Em prod real: DSN setado, traces 10%, releases taggadas via GIT_SHA. init_sentry(environment='production') diff --git a/backend/config/urls.py b/backend/config/urls.py index 9bbe9f8b..cbdaf2a5 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -35,6 +35,10 @@ path('api/v1/', include('apps.moderation.urls')), path('api/v1/', include('apps.newsletter.urls')), path('api/v1/', include('apps.audit.urls')), + # Busca editorial — endpoint próprio sob /api/v1/search/ (ADR-023). + # Extensível para /search/comments/, /search/suggest/ futuros sem + # refactor de URL. + path('api/v1/search/', include('apps.search.urls')), ] if settings.DEBUG: diff --git a/backend/pytest.ini b/backend/pytest.ini index 85f18cdf..957d145b 100644 --- a/backend/pytest.ini +++ b/backend/pytest.ini @@ -17,3 +17,4 @@ markers = slow: marca teste lento (rodar com -m slow) integration: requer DB+Redis reais unit: puramente em memória (rápido) + requires_postgres: requer Postgres real (FTS pt-BR, extensions, GIN); pula em SQLite-dev (ADR-020) diff --git a/backend/seed_users.py b/backend/seed_users.py deleted file mode 100644 index b6346e5a..00000000 --- a/backend/seed_users.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Seed das 4 contas oficiais do Interpop. - - ┌──────────────────────────────────┬──────────┬─────────────────────────────┐ - │ Email │ Role │ Username │ - ├──────────────────────────────────┼──────────┼─────────────────────────────┤ - │ interpop.cc@gmail.com │ admin │ Interpop │ - │ raicabernardo06@gmail.com │ editor │ raica │ - │ cceciliavp@gmail.com │ editor │ cecilia │ - │ davidhrpereira@gmail.com │ editor │ david │ - └──────────────────────────────────┴──────────┴─────────────────────────────┘ - -Senha provisória: `Interpop@2026` (todas as 4). Cada um deve trocar via -/recuperar-senha no primeiro acesso — política de boas práticas. - -Idempotente: rodar de novo só atualiza role/nome (sem recriar nem -reescrever senha existente). Use `--reset-passwords` se quiser forçar. - -Rodar: - cd backend && venv/bin/python manage.py shell -c "exec(open('seed_users.py').read())" -""" - -from apps.users.models import User - -SEED = [ - { - 'email': 'interpop.cc@gmail.com', - 'username': 'Interpop', - 'first_name': 'Interpop', - 'last_name': '', - 'role': User.Role.ADMIN, - }, - { - 'email': 'raicabernardo06@gmail.com', - 'username': 'raica', - 'first_name': 'Raica', - 'last_name': 'Bernardo', - 'role': User.Role.EDITOR, - }, - { - 'email': 'cceciliavp@gmail.com', - 'username': 'cecilia', - 'first_name': 'Cecília', - 'last_name': 'Vieira Pinto', - 'role': User.Role.EDITOR, - }, - { - 'email': 'davidhrpereira@gmail.com', - 'username': 'david', - 'first_name': 'David', - 'last_name': 'Pereira', - 'role': User.Role.EDITOR, - }, -] - -DEFAULT_PASSWORD = 'Interpop@2026' - -created, updated = 0, 0 -for spec in SEED: - user, was_created = User.objects.get_or_create( - email=spec['email'], - defaults={ - 'username': spec['username'], - 'first_name': spec['first_name'], - 'last_name': spec['last_name'], - 'role': spec['role'], - 'is_active': True, - 'is_staff': spec['role'] == User.Role.ADMIN, - 'is_superuser': spec['role'] == User.Role.ADMIN, - }, - ) - if was_created: - user.set_password(DEFAULT_PASSWORD) - user.save() - created += 1 - print(f' ✓ criado: {user.email:<32} role={user.role}') - else: - # Já existia — atualiza role/nome/staff sem mexer em senha - fields = [] - if user.role != spec['role']: - user.role = spec['role'] - user.is_staff = spec['role'] == User.Role.ADMIN - user.is_superuser = spec['role'] == User.Role.ADMIN - fields += ['role', 'is_staff', 'is_superuser'] - if user.username != spec['username']: - user.username = spec['username']; fields.append('username') - if user.first_name != spec['first_name']: - user.first_name = spec['first_name']; fields.append('first_name') - if user.last_name != spec['last_name']: - user.last_name = spec['last_name']; fields.append('last_name') - if fields: - user.save(update_fields=fields) - updated += 1 - print(f' ~ atualiz.: {user.email:<32} ({", ".join(fields)})') - else: - print(f' · ok: {user.email:<32} (sem alteração)') - -print() -print(f'==> {created} criado(s), {updated} atualizado(s)') -if created: - print(f' Senha provisória: {DEFAULT_PASSWORD!r} — trocar via /recuperar-senha') diff --git a/docs/backlog/README.md b/docs/backlog/README.md new file mode 100644 index 00000000..f35267c3 --- /dev/null +++ b/docs/backlog/README.md @@ -0,0 +1,126 @@ +# Backlog — Interpop + +> **Pasta-fonte do "QUEM faz O QUÊ, QUANDO".** Tudo aqui responde "que trabalho está planejado/em execução/feito?". O **porquê** vive em `requirements/`. O **como** vive em `specs/` + ADRs. + +## Hierarquia rastreável (engenharia-de-requisitos) + +``` +Requisito (RF/RNF) ← docs/requirements/ + ↓ realizado por +Epic (EP-NN) ← ESTA pasta, epics/ + ↓ decomposto em +Feature (F-NN) ← ESTA pasta, features/ + ↓ aceito quando +Critério de Aceitação (CA01..CANN) ← dentro do arquivo de Feature + ↓ ilustrado por +User Story (USNN.M) + cenários BDD ← dentro do arquivo de Feature + ↓ implementado por +Task (T / TX) ← dentro do arquivo de Feature + ↓ entregue em +Sprint ← ESTA pasta, sprints/ + ↓ materializada em +Commit (SHA) ← cross-ref no Task +``` + +**Regra dura**: cada nó cita explicitamente o nó **pai** e os nós **filhos** via link relativo. Quando algo é fechado, **move-se** para `done/` (não cópia — `git mv` preserva histórico). + +## Estrutura + +``` +backlog/ +├── README.md este arquivo +├── glossario.md vocabulário de domínio editorial (artigo, editoria, redator, autor, leitor…) +│ +├── epics/ 1 arquivo por Epic — descrição + lista de Features filhas +│ ├── EP-01-fundacao-plataforma.md +│ ├── EP-02-publicacao-editorial.md +│ ├── EP-03-engajamento-comunidade.md +│ ├── EP-04-newsletter-comunicacao.md +│ ├── EP-05-moderacao-comunidade.md +│ ├── EP-06-administracao-sistema.md +│ └── EP-10-busca-editorial.md +│ +├── features/ 1 arquivo por Feature — descrição + CAs + USs (com BDD) + Tasks +│ ├── F-30-busca-texto-livre.md +│ ├── F-31-filtros-busca.md +│ └── F-32-deep-linking-busca.md +│ +├── sprints/ 1 arquivo por Sprint — execução temporal (mapping US/Tasks) +│ ├── sprint-4-busca-editorial.md +│ ├── sprint-5-filtros-deep-linking.md +│ └── sprint-6-supabase-evaluation.md +│ +└── done/ Epics e Features fechados (arquivos MOVIDOS, não copiados) +``` + +## Convenções inegociáveis (alinhadas com engenharia-de-requisitos) + +### Naming (regra dura) + +| Nível | Pode no título | NÃO pode no título | +| ----------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| **Epic** | Substantivo + adjetivo: "Fundação da plataforma", "Busca editorial" | Verbo no infinitivo ("Implementar busca"), termo técnico ("Postgres FTS") | +| **Feature** | Substantivo + adjetivo: "Busca por texto livre" | Infinitivo, sigla técnica ("Implementar tsvector + GIN") | +| **CA** | Estado verificável: "Resultados aparecem em até 300ms" | Vago: "Performance OK" | +| **US** | "Como [persona], quero [ação], para [valor]" pt-BR | Mistura técnico-negócio na frase | +| **Task** | **PODE** usar termo técnico: "Migration 0001 — CONFIGURATION pt_unaccent" | (Tasks são o único nível operacional onde técnico é OK) | + +### IDs canônicos (imutáveis após criação) + +| Tipo | Formato | Exemplo | +| ----------------------- | ------------------------------ | -------------------------- | +| Epic | `EP-NN` | `EP-10` | +| Feature | `F-NN` | `F-30` | +| Critério de Aceitação | `CANN` (dentro do Feature pai) | `CA01`, `CA15` | +| User Story | `USNN.M` | `US30.1`, `US31.4` | +| Task US-bound | `TNN.M.K` | `T30.1.4b` | +| Task transversal | `TX-NN` | `TX-18` | +| Requisito Funcional | `RF-NNN` | `RF-007` | +| Requisito Não-Funcional | `RNF-NN` | `RNF-perf` | +| Sprint | `sprint-N-slug` | `sprint-4-busca-editorial` | + +### Prioridade (em todos os níveis) + +- 🔴 **Imediato** — bloqueia MVP, security crítico, regressão de produção +- 🟠 **Alta** — release atual; idealmente entregue no Sprint corrente +- 🟡 **Normal** — próxima Sprint +- ⚪ **Baixa** — backlog de longa data + +### Definition of Done de Feature + +Uma Feature está **Done** quando: + +1. Todos os CAs estão verificados por automated test (ou manual checklist se for UX puro) +2. Todas as USs têm cenários BDD que rodam verde +3. Todas as Tasks estão `done` com commit hash +4. Code-review aprovado (ver `gsd-code-reviewer` ou humano sênior) +5. Cobertura ≥ gate do Sprint (40% Sprint 1 → 80% Sprint 4+) +6. Documentação cruzada atualizada (RF/RNF citados + Sprint cita Feature + Feature aparece em done/) +7. Mergeada em `main` via PR (sem `--force-push`, sem `--no-verify`) + +## Como fechar uma Feature (workflow) + +1. Confirmar que todos os CAs/USs/Tasks dela estão `✅ Done`. +2. Atualizar tabela em `features/F-NN-nome.md` com commit hashes finais. +3. Atualizar Epic pai (`epics/EP-NN-...md`) mudando a linha da Feature para `✅ Done`. +4. Atualizar Sprint correspondente (`sprints/sprint-N-...md`) mudando status. +5. Atualizar Requisitos realizados (`requirements/RF-NNN-...md` seção "Realizado por"). +6. `git mv docs/backlog/features/F-NN-nome.md docs/backlog/done/F-NN-nome.md` +7. Commit: `chore(backlog): F-NN done — close + archive`. + +## Link com a skill canônica + +[`~/.claude/skills/engenharia-de-requisitos/`](https://github.com/seekdevcore/sk-requirements-engineering-theskill) — IFPB ERS + Sommerville/Pressman/Wiegers/BABOK v3 + Code de Ética 002/2024. + +## Cross-references + +- [Requisitos](../requirements/README.md) — RF/RNF que alimentam os Epics +- [Specs técnicas](../specs/) — DESIGN, ADRs por feature +- [ADRs do projeto](../planning/adrs/) — decisões arquiteturais transversais +- [Architecture overview](../architecture/overview.md) +- [Testing standards](../tests/testing-standards.md) +- [Hosting plan](../planning/HOSTING-DEPLOY-PLAN.md) + +--- + +_Criado em 2026-06-09 como parte da reorganização `chore/docs-reorg`._ diff --git a/docs/backlog/epics/EP-01-fundacao-plataforma.md b/docs/backlog/epics/EP-01-fundacao-plataforma.md new file mode 100644 index 00000000..8ff6db70 --- /dev/null +++ b/docs/backlog/epics/EP-01-fundacao-plataforma.md @@ -0,0 +1,101 @@ +# EP-01 — Fundação da plataforma + +> **Tipo**: Epic +> **Status**: ✅ Done em código (Sprint 1, pre-busca) · 🚧 Documentação retroativa parcial (F-01 detalhada; F-03/F-04/F-05 stubs) +> **Prioridade global**: 🔴 Imediato +> **Owner**: Gabriel Marques +> **Criado em**: Sprint 1 (pre-2026-05) · **Encerrado em**: Sprint 1 (entrega contínua) + +--- + +## Visão de produto + +Tudo o que precisava existir **antes** de qualquer feature editorial: Django funcional com settings split por ambiente, autenticação JWT em cookie httpOnly com hierarquia de papéis, observability mínima (`/healthz/`, structlog JSON, AuditLog, Sentry), CI verde com gates de cobertura, frontend bootstrap com Vite + React 19 + TypeScript, deploy reproduzível no Hostinger KVM 1. + +Esta fundação é **a razão** de F-30 (busca editorial) ter sido entregue em 7 dias — sem ela, busca exigiria 4 semanas refazendo auth, CI, observability do zero. + +**Recorte honesto deste Epic**: EP-01 cobre a **fundação técnica horizontal**. Ferramentas administrativas (hierarquia operada, banimento direto, management commands de staff) vivem em [EP-06](EP-06-administracao-sistema.md) porque tocam autoridade editorial, não infraestrutura. + +--- + +## Requisitos realizados (rastreabilidade ↑) + +| ID | Requisito | Tipo | Cobertura por este Epic | +| -------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------- | ---------------------------------------------------------- | +| [RF-005](../../requirements/RF/RF-005-users-auth.md) | Autenticação e autorização de usuários (registro, login, rotação, recuperação, papéis) | Funcional | **Parcial** — fluxos do leitor; ferramentas staff em EP-06 | +| [RF-006](../../requirements/RF/RF-006-audit.md) | Sistema registra eventos sensíveis em log auditável | Funcional | Total — AuditLog está nesta fundação | +| [RNF-security](../../requirements/RNF/RNF-security.md) | OWASP Top 10 baseline, CSRF, headers seguros, secret scan em CI | Segurança | Total (gates em F-04) | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | `/healthz/` + UptimeRobot + runbooks de deploy/restart | Disponibilidade | Total (em F-03) | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | Bundle ≤ 500 KB gz, p95 backend ≤ 300ms, baseline Lighthouse | Performance | Parcial — baseline em F-05; gates em CI futuro | + +--- + +## Features sob este Epic (rastreabilidade ↓) + +| ID | Nome | Sprint | Status | Doc | +| ---- | ---------------------------------------------------------------- | ------ | ------------------------------------ | -------------------------------------------------------------------------------------- | +| F-01 | **Autenticação JWT em cookie httpOnly** | 1 | ✅ Done | [F-01](../features/F-01-autenticacao-jwt-cookie-httponly.md) — doc retroativa completa | +| F-02 | Bootstrap Django + uv + DRF + settings split | 1 | ✅ Done · 🚧 Doc retroativa pendente | _stub_ (housekeeping futuro) | +| F-03 | Observability (structlog JSON + Sentry + AuditLog + `/healthz/`) | 1 | ✅ Done · 🚧 Doc retroativa pendente | _stub_ (housekeeping futuro) | +| F-04 | CI + cobertura 40% gate + SAST (bandit/semgrep) + secret scan | 1 | ✅ Done · 🚧 Doc retroativa pendente | _stub_ (housekeeping futuro) | +| F-05 | Frontend bootstrap (Vite + React 19 + tsc + ESLint + Prettier) | 1 | ✅ Done · 🚧 Doc retroativa pendente | _stub_ (housekeeping futuro) | + +> **Prioridade da retroatividade**: F-01 foi detalhada agora porque concentra o maior risco aberto (S-02, S-04, S-06 — débitos de segurança). F-02/F-03/F-04/F-05 entram em Sprint de housekeeping dedicado quando o ciclo de produto permitir — não bloqueiam Sprint 5. + +--- + +## Métricas de sucesso do Epic + +| Métrica | Alvo | Status | +| ---------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------- | +| Tempo até primeira feature de produto | ≤ 4 semanas pós-bootstrap | ✅ — F-30 (busca) entregue em 7 dias após Sprint 1 | +| Cobertura de testes backend (gate CI) | ≥ 40% Sprint 1 | ✅ — 82% efetivo (88 testes) | +| `/healthz/` uptime medido externamente | ≥ 99.5% mensal | ✅ — UptimeRobot ativo desde Sprint 1 | +| CI bloqueia PR com queda de cobertura | Gate ativo | ✅ — `.github/workflows/ci.yml` falha se cobertura desce | +| Auth funcional sem regressão em produção | 0 incidentes Sev-1/2 em auth desde Sprint 1 | ✅ — C1 (rotação silenciosa) e C3 (atomicidade reset) eram bugs latentes, não incidentes prod | + +--- + +## ADRs relacionadas + +- [ADR-005 Hostinger KVM 1](../../planning/adrs/ADR-005-hostinger-kvm1.md) — hospedagem +- [ADR-006 DevSecOps embedded](../../planning/adrs/ADR-006-devsecops-embedded.md) — security no PR loop +- [ADR-008 DPO/LGPD baseline](../../planning/adrs/ADR-008-dpo-lgpd-baseline.md) — princípios LGPD que F-01 herda +- [ADR-010 `/api/v1/` versioning](../../planning/adrs/ADR-010-api-v1-versioning.md) — prefixo de todas as rotas (inclusive `/api/v1/auth/*`) +- [ADR-012 Integridade transacional](../../planning/adrs/ADR-012-integridade-transacional.md) — atomicidade em mutações multi-tabela (origina fix C3 de F-01) +- [ADR-013 Observability gate](../../planning/adrs/ADR-013-observability-gate.md) — `/healthz/` + structlog + Sentry obrigatórios + +--- + +## Sprints envolvidas + +| Sprint | Escopo | Status | +| -------- | ---------------------------------------------------------------------- | ------------------------------------- | +| Sprint 1 | F-01 + F-02 + F-03 + F-04 + F-05 (todas em paralelo, fundação técnica) | ✅ entregue (pre-busca, pre-Sprint 4) | +| Sprint 4 | F-30 busca — primeira feature de produto sobre esta fundação | ✅ entregue 2026-06-09 (PR #37) | + +--- + +## Histórico de mudanças + +| Data | Evento | +| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Sprint 1 | Epic executado em código — F-01 a F-05 entregues em paralelo. Fundação habilitou as Sprints subsequentes (Sprint 2-3 produto editorial, Sprint 4 busca). | +| 2026-05-XX | Fix C1 (rotação silenciosa) + Fix C3 (atomicidade reset) — débitos identificados em revisão do `Improvement-system.md` §11.1. | +| 2026-06-09 | Documentação retroativa: F-01 detalhada (CAs, USs, BDD, Tasks); F-02/F-03/F-04/F-05 mantidas como stubs para Sprint de housekeeping futuro. | + +--- + +## Cross-references + +- [Sprint 1 (Improvement-system.md §6)](../../planning/Improvement-system.md) — registro operacional original +- [F-01 Autenticação JWT em cookie httpOnly](../features/F-01-autenticacao-jwt-cookie-httponly.md) — única Feature deste Epic com doc retroativa completa +- [EP-06 Administração do sistema](EP-06-administracao-sistema.md) — Epic complementar para ferramentas operadas de staff (banimento, comandos) +- [Architecture overview](../../architecture/overview.md) +- [DESIGN.md users-auth](../../specs/users-auth/DESIGN.md) +- [RF-005](../../requirements/RF/RF-005-users-auth.md), [RF-006](../../requirements/RF/RF-006-audit.md) +- [CLAUDE.md §4](../../../CLAUDE.md) — convenções de hierarquia `dev > admin > editor > user` + +--- + +_Última atualização: 2026-06-09 (doc retroativa parcial). Próxima ação: detalhar F-02/F-03/F-04/F-05 em Sprint de housekeeping; aplicar hotfix S-02 (`JWT_SIGNING_KEY` hard-fail) replicando padrão F2-B-03 da busca._ diff --git a/docs/backlog/epics/EP-02-publicacao-editorial.md b/docs/backlog/epics/EP-02-publicacao-editorial.md new file mode 100644 index 00000000..7ee3178c --- /dev/null +++ b/docs/backlog/epics/EP-02-publicacao-editorial.md @@ -0,0 +1,131 @@ +# EP-02 — Publicação editorial + +> **Tipo**: Epic +> **Status**: ✅ Done em produção (Sprint 1-2, pre-busca) · 🚧 manutenção contínua (OPS-1 hotfix candidato) +> **Prioridade global**: 🔴 Imediato (núcleo editorial — sem isto não há produto) +> **Owner**: Gabriel Marques +> **Criado em**: Sprint 1 (bootstrap) · **Formalizado retroativamente**: 2026-06-09 + +--- + +## Visão de produto + +Interpop é veículo editorial brasileiro de análise crítica de Soft Power e cultura pop. **Publicação editorial é o produto** — sem ela, não há leitor, retenção, newsletter, comentário ou busca. Este Epic materializa o ciclo completo: editor cria rascunho, publica, sistema avisa assinantes, leitor encontra via URL humana com slug em português, redes sociais mostram cartão rico, ranking "mais lidos" reflete leitura real (não F5). + +Cinco editorias canônicas (Música, Moda, Cinema, Literatura, Cultura Digital) sustentam a identidade visual e taxonômica da marca. Vocabulário fixo é decisão deliberada (ADR-002): caos taxonômico de tag livre dilui SEO e identidade. + +KPIs sustentados por este Epic: + +- 100% dos artigos com `status=published` têm `published_at` preenchido (invariante I1) — **achado OPS-1**: hoje só vale via API; admin Django deixa NULL +- Cartão social renderiza corretamente em ≥ 95% dos compartilhamentos (Twitterbot, WhatsApp, facebookexternalhit, LinkedInBot, Slackbot, Discordbot, TelegramBot, Pinterest, redditbot, Applebot) +- `view_count` reflete leitura real — bucket anti-abuse 5min/(article,IP) impede inflação +- Apenas 1 artigo em destaque (`is_featured=True`) por vez — garantido por `transaction.atomic` em `Article.save()` + +--- + +## Requisitos realizados (rastreabilidade ↑) + +| ID | Requisito | Tipo | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------- | +| [RF-001](../../requirements/RF/RF-001-articles.md) | Sistema permite publicação editorial + leitura via slug humano + OG meta + view_count | Funcional | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | Listing público p95 ≤ 200ms server; LCP/INP/CLS dentro dos gates | Performance | +| [RNF-security](../../requirements/RNF/RNF-security.md) | OWASP + `IsPublisherOrReadOnly` + `IsOwnerOrAdmin` object-level + escape XSS em body | Segurança | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | WCAG 2.2 AA na página de artigo + landmarks + contraste | Acessibilidade | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | IP do leitor usado apenas como chave de bucket; não persistido em log de leitura | LGPD | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Crawler social funciona mesmo com SPA quebrada (middleware server-side) | Disponibilidade | + +--- + +## Features sob este Epic (rastreabilidade ↓) + +| ID | Nome | Sprint | Status | Arquivo | +| ---- | ------------------------------- | ------ | ------------------------------- | ------------------------------------------------------ | +| F-10 | Publicação e leitura de artigos | 1-2 | ✅ Done (Sprint 1-2, pre-busca) | [F-10](../features/F-10-publicacao-leitura-artigos.md) | + +> **Decisão deliberada de granularidade**: F-10 consolida 4 capacidades em **uma única Feature** (publicação, leitura, view_count anti-abuse, OG meta + crawler middleware) porque na realidade do código essas 4 responsabilidades vivem no mesmo app (`apps.articles`) e foram entregues juntas no mesmo Sprint. Decompor retroativamente em F-10/F-11/F-12/F-13 seria ficção documental. A própria spec retroativa ([DESIGN.md](../../specs/articles/DESIGN.md) §0) reconhece esse acoplamento como **dívida arquitetural OPS-2** (candidato a `apps/seo/` futuro), não como design intencional. + +### Sub-capacidades cobertas por F-10 + +| Sub-capacidade | Sub-CAs em F-10 | Componentes técnicos | +| ---------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| ✅ Publicação editorial (draft → published) | CA01-CA04 | `ArticleWriteSerializer`, `ArticleDetailView.perform_update`, signals `pre_save`+`post_save`, Celery dispatch (ADR-009) | +| ✅ Leitura via URL com slug humano | CA02, CA05 | Conversor `` (`converters.py`), rota `/noticia/`, `_unique_slug()` auto-sufixa | +| ✅ Categorização editorial (5 editorias fixas) | CA08, CA09 | `Category` model, data migration `0003_seed_pop_culture_categories`, admin `prepopulated_fields` | +| ✅ View_count anti-abuse | CA06, CA12 | `ArticleViewCountView`, bucket `cache.add(key, True, 300)`, `apps.audit.get_client_ip`, `F('view_count')+1` atômico | +| ✅ OG meta tags + crawler middleware | CA07 | `SocialOGMiddleware`, regex `_CRAWLER_RE` (10 user agents), `_render_og_html`, sitemap.xml + robots.txt apontando para frontend (`SITE_URL`) | +| ✅ Hero único (apenas 1 featured por vez) | CA11 | `Article.save()` override + `transaction.atomic` (ADR-012) | + +--- + +## Métricas de sucesso do Epic + +| Métrica | Alvo | Como medir | Status | +| ------------------------------------------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | +| 100% dos artigos `status=published` têm `published_at != NULL` (invariante I1) | 100% | Query `Article.objects.filter(status='published', published_at__isnull=True).count() == 0` | 🟠 **violado por admin Django** (OPS-1 — hotfix candidato) | +| Apenas 1 artigo `is_featured=True` por vez (invariante I2) | 100% | `Article.objects.filter(is_featured=True).count() <= 1` | ✅ ok (testes `test_marking_article_featured_unsets_previous` + `test_only_one_featured_after_multiple_marks`) | +| Editor X não toca artigo de editor Y via API (invariante I7) | 100% | `IsOwnerOrAdmin` object-level + testes `test_editor_cannot_update_other_editors_article`, `_cannot_delete_` | ✅ ok | +| Cartão social renderiza OG meta em crawler conhecido (invariante I5) | ≥ 95% UA hits | Manual via `curl -A "WhatsApp/2.0" .../noticia//` em prod | 🟡 **sem teste automatizado** (GAP-1) | +| View_count não infla por F5 do mesmo IP em 5min (invariante I4 + I8) | bucket OK | Teste `test_view_count_incremented_once_per_5min_window`; em prod limitado por LocMemCache per-worker (S-06) | 🟡 **vaza entre workers gunicorn** (S-06 — A20 Redis resolve) | +| Crawler de WhatsApp resolve preview em ≤ 1.5s sob carga | p95 ≤ 1.5s | Sentry transactions tag `crawler:true` | ⏳ baseline pendente | + +--- + +## ADRs relacionadas (decisões locked-in) + +ADRs vivem em [`docs/planning/Improvement-system.md`](../../planning/Improvement-system.md) (gitignored — débito O-01 em CONCERNS). Destaques por camada: + +- **Domínio**: ADR-002 (tags livres deferidas), ADR-014 (`body` texto puro), ADR-012 (`transaction.atomic` em save com side-effect múltiplo) +- **Backend**: ADR-010 (prefixo `/api/v1/`), ADR-009 (newsletter Celery `.delay()` substituiu chamada síncrona — C12) +- **Read-projection vizinha**: ADR-018 (`SearchIndex` mantido por trigger PL/pgSQL, não signal) +- **Operacional**: ADR-020 (UUID em PK), A20 (Redis para cache distribuído — resolve S-06) + +--- + +## Sprints envolvidas + +| Sprint | Escopo | Status | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| Sprint 1 | Bootstrap `apps.articles`: `Article` + `Category` models, migrations 0001/0002, serializers, views, admin, URLs `/api/v1/` | ✅ Done | +| Sprint 2 | Pivô editorial: 5 categorias canônicas (`0003`), `cover_caption` obrigatório (`0004`), `SocialOGMiddleware`, sitemap.xml + robots.txt, conversor `` unicode | ✅ Done | +| Sprint 4 | Refactor adjacente: `send_article_notification` migrou para Celery `.delay()` (C12); read-projection `apps.search` via trigger PL/pgSQL (ADR-018) — **não toca F-10 diretamente**, mas afeta fluxo de publicação | ✅ Done | + +--- + +## Débitos ativos (cross-ref [CONCERNS.md](../../specs/codebase/CONCERNS.md)) + +| # | Item | Severidade | Quando virar Sprint? | +| ----- | -------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------- | +| OPS-1 | Publicar via Django admin **não preenche `published_at`** (artigo somem da ordenação default `-published_at, -created_at`) | 🟠 Alta | **Hotfix candidato Sprint 5** (override `ArticleAdmin.save_model` ou mover lógica para `Article.save()`) | +| S-01 | `body` aceita HTML cru no boundary serializer (React escapa hoje, mas é defesa única) | 🔴 Crítica | Quando `body` virar JSON estruturado (ADR-014) | +| S-06 | `view_count` bucket vaza entre workers gunicorn (LocMemCache per-process — 3 workers infla 3×) | 🟠 Alta | A20 — Redis para cache distribuído | +| D-10 | Newsletter envia link de capa quebrado (`cover_image.url` é relativo, email não resolve) | 🟠 Alta | Hotfix candidato Sprint 5 | +| OPS-2 | Acoplamento de responsabilidades em `apps.articles` (SEO + sitemap + robots + middleware OG) | 🟡 Normal | Refactor para `apps/seo/` quando custo justificar | +| OPS-3 | `SITE_URL` com fallback `localhost:5173` hardcoded em 3 lugares; esquecer var em prod publica URL de dev | 🟠 Alta | `raise ImproperlyConfigured` em `production.py` | +| GAP-1 | Sem teste automatizado para crawler social vê OG meta (regressão silenciosa no middleware) | 🟡 Normal | Sprint de hardening de testes | + +--- + +## Histórico de mudanças + +| Data | Evento | +| ---------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| Sprint 1 | Epic iniciado implicitamente no bootstrap de `apps.articles` (sem documentação formal — débito retroativo) | +| Sprint 2 | Pivô editorial cristalizou 5 editorias canônicas; `SocialOGMiddleware` + sitemap entram em produção | +| Sprint 4 | Newsletter virou assíncrona (Celery `.delay()`); `apps.search` instala trigger PL/pgSQL como read-projection | +| 2026-06-09 | EP-02 formalizado retroativamente (esta entrega); cross-ref RF-001 + F-10 + [DESIGN.md](../../specs/articles/DESIGN.md) materializadas | + +--- + +## Cross-references + +- [RF-001](../../requirements/RF/RF-001-articles.md) — requisito de negócio +- [F-10](../features/F-10-publicacao-leitura-artigos.md) — Feature consolidada (4 sub-capacidades) +- [DESIGN.md](../../specs/articles/DESIGN.md) — spec retroativo (fonte de verdade técnica) +- [CONCERNS.md](../../specs/codebase/CONCERNS.md) — débitos S-01, S-06, S-11, D-07, D-10, OPS-1..3 +- [ARCHITECTURE.md](../../specs/codebase/ARCHITECTURE.md) — grafo de apps (Ca=4 / Ce=3 / I=0.43) +- [Improvement-system.md](../../planning/Improvement-system.md) — backlog mestre pré-reorg +- Epics vizinhos: [EP-03 engajamento](EP-03-engajamento-comunidade.md) (`comments` consome Article), [EP-04 newsletter](EP-04-newsletter-comunicacao.md) (signal `post_save` enfileira `.delay()`), [EP-10 busca editorial](EP-10-busca-editorial.md) (read-projection via trigger PL/pgSQL) + +--- + +_Formalizado retroativamente em 2026-06-09. EP-02 é Epic de **infraestrutura editorial** — quase estável, mas com 2 hotfix candidates (OPS-1, D-10) que justificam Sprint de housekeeping antes de evoluções maiores._ diff --git a/docs/backlog/epics/EP-03-engajamento-comunidade.md b/docs/backlog/epics/EP-03-engajamento-comunidade.md new file mode 100644 index 00000000..122d214a --- /dev/null +++ b/docs/backlog/epics/EP-03-engajamento-comunidade.md @@ -0,0 +1,98 @@ +# EP-03 — Engajamento da comunidade + +> **Tipo**: Epic +> **Status**: 🚧 Parcial — F-20 ✅ Done (Sprint 2-3); F-21/F-22 ⏳ Backlog +> **Prioridade global**: 🟠 Alta +> **Owner**: Gabriel Marques +> **Criado em**: pré-Sprint 1 (implementação retroativa formalizada em 2026-06-09) + +--- + +## Visão de produto + +Interpop não é um portal de notícias rápidas — é análise editorial de leitura longa. O engajamento que importa não é volume bruto de cliques, é **conversa pública qualificada** sobre o artigo: leitor discorda, complementa, corrige, manifesta ressonância. Este Epic constrói o espinhaço dessa conversa. + +O conjunto inicial entrega o mínimo viável (F-20: comentar, responder, curtir, remover o próprio). Os incrementos futuros (F-21 notificações, F-22 anti-spam) reforçam retenção e qualidade do canal **sem** abrir a porta para threading recursivo nem comentário anônimo — decisões deliberadas em RF-002 para preservar o caráter editorial. + +KPI alvo: + +- ≥ 25% dos leitores autenticados comentam ao menos uma vez em 30 dias +- ≥ 40% dos artigos publicados recebem ao menos 1 comentário em 7 dias após publicação +- Taxa de remoção por admin ≤ 5% do total de comentários criados (sinaliza saúde da moderação reativa) + +--- + +## Requisitos realizados (rastreabilidade ↑) + +Este Epic executa os seguintes requisitos: + +| ID | Requisito | Tipo | +| -------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------- | +| [RF-002](../../requirements/RF/RF-002-comments.md) | Sistema permite leitor autenticado comentar, responder, curtir e remover | Funcional | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Auth obrigatória, defesa em profundidade `IsNotBanned`, OWASP | Segurança | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Form e botões de comentário/curtida acessíveis | Acessibilidade | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Soft-delete preserva audit trail · retenção formal pendente | LGPD | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Falha de `apps.comments` não derruba leitura do artigo | Disponibilidade | + +--- + +## Features sob este Epic (rastreabilidade ↓) + +| ID | Nome | Sprint | Status | Arquivo | +| ---- | ------------------------------------- | ------- | ------------------------------- | -------------------------------------------------- | +| F-20 | Comentários e curtidas | 2-3 | ✅ Done (pre-busca) | [F-20](../features/F-20-comentarios-e-curtidas.md) | +| F-21 | Notificações por resposta | Backlog | ⏳ Pending — decisão de produto | _(arquivo futuro)_ | +| F-22 | Anti-spam reativo (filtro + honeypot) | Backlog | ⏳ Pending — depende de volume | _(arquivo futuro)_ | + +> **F-21** (notificações por resposta): aciona quando leitor é respondido. Engenharia: signal no `Comment.save` → `apps.newsletter` ou futuro `apps.notifications`. Decisão de produto pendente: canal (e-mail vs in-app vs ambos), opt-out. +> +> **F-22** (anti-spam): hoje o controle é 100% reativo (admin bana após observar). Backlog quando volume sustentar dor: regex de URL → honeypot → rate-limit específico `comments_create` (S-07 do DESIGN) → eventualmente Akismet/ML. + +--- + +## Métricas de sucesso do Epic + +| Métrica | Alvo | Como medir | Status | +| --------------------------------- | ------------------------------------- | -------------------------------------------------------------- | -------------------- | +| Participação | ≥ 25% MAU autenticado comenta em 30d | Analytics próximo Sprint | ⏳ baseline pendente | +| Cobertura de artigos | ≥ 40% artigos com 1+ comentário/7d | Query agregada `Comment.objects.filter(...)` | ⏳ baseline pendente | +| Saúde da moderação | ≤ 5% taxa de remoção por admin | `Comment.objects.filter(is_deleted, deleted_by__role='admin')` | ⏳ baseline pendente | +| Resposta a defesa em profundidade | 0 comentários criados por banido | Test `test_create_comment_banned_user_returns_403` | ✅ | +| Idempotência de like | 0 likes duplicados por (user,comment) | `unique_together` enforced no DB | ✅ | + +--- + +## ADRs relacionadas (decisões locked-in) + +Não há `adrs/comments/` formal — decisões vivem no [DESIGN.md](../../specs/comments/DESIGN.md) §3-5 e foram implementadas pré-formalização do processo de ADR. Destaques recuperados retroativamente: + +- **Soft-delete preserva linha** (DESIGN §4.3) — `perform_destroy` faz `UPDATE is_deleted=true` em vez de `DELETE` físico, preservando audit trail +- **1 nível de aninhamento** (DESIGN §3.2, I8) — `validate_parent_id` exige `parent=None` no candidato +- **Sem `signals.py` no módulo** (DESIGN §3.3) — toda escrita síncrona no request thread; sem fan-out de notificações +- **`IsNotBanned` em escrita** (Improvement-system §11.6 S8) — defesa em profundidade contra usuário banido +- **`unique_together` para idempotência de like** (DESIGN §2.2) — corrida de duplo-clique é atomicamente segura + +> **Débito de ADR formal**: as 5 decisões acima precisam virar ADR-NNN em `docs/specs/comments/adrs/` quando o Sprint de housekeeping de moderação rodar. + +--- + +## Sprints envolvidas + +| Sprint | Escopo | Status | Arquivo | +| ---------- | -------------------------------------------------------------------------- | --------------------- | --------------------------- | +| Sprint 2-3 | F-20 implementada (pré-formalização) | ✅ entregue em código | _(retroativo, sem arquivo)_ | +| Backlog | F-21 + F-22 + sprint de moderação (S-01, S-07, OPS-1, OPS-2, GAP-1, GAP-2) | ⏳ planejado | _(sprint futuro)_ | + +--- + +## Histórico de mudanças + +| Data | Evento | +| ---------- | ------------------------------------------------------------------------------------------------- | +| Sprint 2-3 | Módulo `apps.comments` implementado em código (sem RF/Epic/Feature formalizados) | +| 2026-06-09 | Spec retroativo `docs/specs/comments/DESIGN.md` produzido (3 achados críticos: S-01, S-07, OPS-1) | +| 2026-06-09 | EP-03 expandido de stub para versão completa; F-20 documentada retroativamente | + +--- + +_Última atualização: 2026-06-09 — documentação retroativa pós-fechamento de Sprint 4 (busca). Próxima ação: priorizar Sprint de moderação para limpar S-01 (sanitização HTML) e S-07 (throttle `comments_create`) — ambos hotfix candidates._ diff --git a/docs/backlog/epics/EP-04-newsletter-comunicacao.md b/docs/backlog/epics/EP-04-newsletter-comunicacao.md new file mode 100644 index 00000000..0470f6f1 --- /dev/null +++ b/docs/backlog/epics/EP-04-newsletter-comunicacao.md @@ -0,0 +1,110 @@ +# EP-04 — Newsletter e comunicação + +> **Tipo**: Epic +> **Status**: 🟡 Em produção (F-40 ✅ Done · F-41/F-42/F-43 ⏳ Sprint 8+) +> **Prioridade global**: 🟠 Alta +> **Owner**: Gabriel Marques +> **Criado em**: Sprint 2 (implementação) · **Documentação retroativa**: 2026-06-09 + +--- + +## Visão de produto + +Newsletter editorial é o **único canal owned** do Interpop — única forma de alcançar leitor sem depender de SEO, algoritmo de rede social ou retorno espontâneo. Cobre 4 momentos: + +1. **Captura de opt-in público** (footer + modal): leitor digita email, pronto. Sem cadastro de conta, sem captcha, sem fricção. +2. **Confirmação imediata** via welcome email transacional contendo link de descadastro (LGPD compliance desde o 1º contato). +3. **Notificação por artigo publicado** com fan-out para 100% da lista ativa via Celery worker — nunca trava o publish. +4. **Cancelamento 1-clique** via token UUID estável (preservado entre cancel/re-subscribe). + +A newsletter é a única feature que faz **fan-out de email baseado em estado de domínio** (`Article.status`). O resto do tráfego transacional (password reset, ban notify) é 1:1 por evento. + +### KPIs alvo (quando F-41 instrumentação existir) + +| Métrica | Alvo | Bloqueio atual | +| ----------------- | --------------- | ------------------------------------------------------------ | +| Open rate | ≥ 35% | Sem pixel/UTM hoje (F-41 Sprint 8 + opt-in LGPD obrigatório) | +| Click-through | ≥ 8% | Sem UTM padronizado (F-41) | +| Churn ≤ 30d | ≤ 5% | Sem `unsubscribed_at` carimbado (L-02) | +| Reputação SMTP | ≥ 95% delivered | Sem bounce handling (GAP-1 + bloqueio INT-1 SendGrid) | +| Crescimento lista | +15% MoM | Métrica ativa (subscribed_at suficiente — funciona hoje) | + +--- + +## Requisitos realizados (rastreabilidade ↑) + +| ID | Requisito | Tipo | +| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------- | +| [RF-004](../../requirements/RF/RF-004-newsletter.md) | Sistema permite que leitor se cadastre, receba notificação por artigo, e se descadastre via link único | Funcional | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Throttle anon no subscribe; token UUID 122 bits; CSRF AllowAny justificado pelo design 1-clique | Segurança | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Opt-in explícito; descadastro acessível em todo email; direito ao esquecimento via unsubscribe | LGPD | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Celery worker absorve falha SMTP; subscribe response sempre 200 mesmo se welcome falhar; teto Gmail 500/dia | Disponibilidade | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Templates HTML inline-CSS Outlook-safe; multipart com texto puro; alt em imagens | Acessibilidade | + +--- + +## Features sob este Epic (rastreabilidade ↓) + +| ID | Nome | Sprint | Status | Arquivo | +| -------- | ------------------------------------------------------------ | ------ | --------------------------------------------- | ------------------------------------------------ | +| **F-40** | **Newsletter editorial** (este PR — documentação retroativa) | 2-3 | ✅ Done (em produção desde Sprint 3) | [F-40](../features/F-40-newsletter-editorial.md) | +| F-41 | Bounce handling + open rate tracking | 8 | ⏳ Pending — bloqueada por INT-1 (SendGrid) | _(a criar quando Sprint 8 iniciar)_ | +| F-42 | Segmentação por editoria | 8 | ⏳ Pending | _(a criar quando Sprint 8 iniciar)_ | +| F-43 | A/B subject lines | 8+ | ⏳ Pending — depende de F-41 (instrumentação) | _(a criar quando Sprint 8+ iniciar)_ | + +### Hotfix candidatos pre-Sprint 8 (descobertos em DESIGN retroativo 2026-06-09) + +> Estes 2 bugs invisíveis quebram funcionalidade silenciosamente. **Recomendado entrar antes de F-41/F-42/F-43.** + +| ID | O quê | Severidade | Tracking | +| -------- | ----------------------------------------------------------------------------------------------------------- | ---------- | ---------- | +| HOTFIX-1 | `cover_image.url` relativa em `article_notification.html:31` quebra imagens em **todos** os emails sentidos | 🔴 | F-40 BUG-1 | +| HOTFIX-2 | `send_welcome` `except Exception: return False` mata `autoretry_for` da Celery task | 🔴 | F-40 BUG-2 | + +--- + +## ADRs relacionadas (decisões locked-in) + +- **ADR-001** — Celery em vez de ThreadPoolExecutor para envio assíncrono +- **ADR-004** — SendGrid como provider transacional (⚠️ **declarada, não implementada** — produção usa Gmail SMTP; INT-1) +- **ADR-009** — Gate Celery + retry policy padrão (`autoretry_for=(Exception,)` + backoff) +- **ADR-010** — Prefixo `/api/v1/` em todos endpoints + +--- + +## Sprints envolvidas + +| Sprint | Escopo | Status | +| --------- | ----------------------------------------------------------------------------------------------------- | ------------ | +| Sprint 2 | Modelo `NewsletterSubscriber` + endpoints subscribe/unsubscribe + welcome email | ✅ entregue | +| Sprint 3 | Signal cross-app em `articles.signals` + task `send_article_notification` + fix bug C2 (double-email) | ✅ entregue | +| Sprint 8 | F-41 (bounce + open rate) + F-42 (segmentação) — depende de migração SendGrid + opt-in LGPD granular | ⏳ planejado | +| Sprint 8+ | F-43 A/B subject lines — depende de F-41 | ⏳ planejado | + +--- + +## Histórico de mudanças + +| Data | Evento | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------- | +| Sprint 2 | Inscrição + welcome + endpoints REST implementados (`models.py`, `views.py`, `serializers.py`, `tasks.py:send_welcome_email`) | +| Sprint 3 | Notificação por artigo publicado via signal cross-app reverso em `apps.articles.signals.py:42-64` (fix bug C2 histórico) | +| 2026-05-29 | BUG-1 observado: `cover_image.url` relativa em `article_notification.html:31` quebra imagens em emails enviados | +| 2026-06-09 | BUG-2 confirmado: `send_welcome` `except Exception` em `services.py:58` mata `autoretry_for` da Celery task | +| 2026-06-09 | DESIGN.md retroativo publicado em `docs/specs/newsletter/DESIGN.md` (9 seções + invariantes + débitos + runbook) | +| 2026-06-09 | EP-04 + F-40 + RF-004 preenchidos retroativamente (chore/docs-reorg PR #39) | + +--- + +## Cross-references + +- [DESIGN técnico completo](../../specs/newsletter/DESIGN.md) — fonte de verdade arquitetural +- [RF-004 Newsletter editorial](../../requirements/RF/RF-004-newsletter.md) — requisito de negócio +- [F-40 Newsletter editorial](../features/F-40-newsletter-editorial.md) — única Feature entregue até aqui +- [INTEGRATIONS.md §SendGrid](../../specs/codebase/INTEGRATIONS.md) — config real vs declarada (INT-1) +- [CONCERNS.md](../../specs/codebase/CONCERNS.md) — débitos INT-1, L-01/L-02, GAP-1/3/4/5 +- [Improvement-system.md](../../planning/Improvement-system.md) — backlog mestre pré-reorg + +--- + +_Última atualização: 2026-06-09. Próxima ação: agendar 2 hotfixes (BUG-1/BUG-2) antes de abrir Sprint 8 com F-41._ diff --git a/docs/backlog/epics/EP-05-moderacao-comunidade.md b/docs/backlog/epics/EP-05-moderacao-comunidade.md new file mode 100644 index 00000000..c3084acf --- /dev/null +++ b/docs/backlog/epics/EP-05-moderacao-comunidade.md @@ -0,0 +1,109 @@ +# EP-05 — Moderação da comunidade + +> **Tipo**: Epic +> **Status**: ✅ Realizado em código (Sprint 2-3, pre-busca) · 📝 Documentação retroativa concluída 2026-06-09 +> **Prioridade global**: 🟠 Alta +> **Owner**: Gabriel Marques +> **Criado em**: Sprint 2 (mai/2026) · **Encerramento operacional**: Sprint 3 (mai/2026) + +--- + +## Visão de produto + +Interpop tem seção de comentários abertos a leitores autenticados. Sem ferramenta de moderação ativa, comportamentos abusivos (spam, discurso de ódio, ataque pessoal a redator) ficam visíveis, contaminam a conversa pública e expõem a marca. O Epic de Moderação da Comunidade entrega o **fluxo dual** que respeita a hierarquia do produto: + +- **Admin bana direto** — em violação clara, sem precisar pedir permissão. +- **Editor solicita banimento** — formaliza o caso com justificativa, admin decide com 2º par de olhos. + +Sustenta também as **invariantes inegociáveis** da hierarquia `dev > admin > editor > user`: dev é imune por design; admin é imune entre si (só dev baneia admin); editor age só sobre leitor; ninguém bana a si mesmo. Defesa em 3 camadas (queryset, validate, service) garante que bug isolado não derruba a invariante. + +KPI alvo pós-launch: + +- < 0.5% comentários removidos manualmente por dia (proxy de qualidade da comunidade) +- 100% das decisões de banimento com trilha de auditoria (ator, alvo, IP, UA, timestamp) +- 0 incidentes de admin banindo admin (invariante I2 nunca viola) +- 100% dos `BanRequest` criados notificam ao menos 1 admin via email em ≤ 5min + +--- + +## Requisitos realizados (rastreabilidade ↑) + +Este Epic executa os seguintes requisitos: + +| ID | Requisito | Tipo | +| -------------------------------------------------------------- | --------------------------------------------------------------------- | --------------- | +| [RF-003](../../requirements/RF/RF-003-moderation.md) | Sistema permite ban direto + BanRequest com invariantes de hierarquia | Funcional | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Defesa em 3 camadas; permissões DRF; trilha de auditoria | Segurança | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Falha do worker celery (email) não bloqueia abertura de BanRequest | Disponibilidade | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Razão do ban (dado pessoal sensível) com retenção 5 anos | LGPD | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Painel admin acessível por teclado (parcial — admin Django nativo) | Acessibilidade | + +--- + +## Features sob este Epic (rastreabilidade ↓) + +| ID | Nome | Sprint | Status | Arquivo | +| ---- | ---------------------------------------------------- | --------- | -------------------------------------------------------- | --------------------------------------------------- | +| F-50 | Ban + BanRequest workflow | 2-3 | ✅ Done (pre-busca) | [F-50](../features/F-50-ban-banrequest-workflow.md) | +| F-51 | Notificação por email do banido (aprovado/rejeitado) | 8 (prev.) | ⏳ Backlog | (a criar quando entrar em sprint) | +| F-52 | Fluxo de contestação do banido (appeal) | — | ⏳ Backlog (sem sprint — depende de volume) | (a criar) | +| F-53 | Auto-expiração de banimento temporário | — | ⏳ Backlog (depende de ADR sobre `expires_at` no schema) | (a criar) | + +--- + +## Métricas de sucesso do Epic + +| Métrica | Alvo | Como medir | Status | +| ------------------------------------ | -------------------------------------------------------- | --------------------------------------------- | ---------------------------- | +| Cobertura de auditoria | 100% decisões com ator + alvo + IP + UA + timestamp | Query em `audit_log` cruzada com `bans` table | ✅ | +| Invariante I2 (admin não bana admin) | 0 violações | Test `test_ban_hierarchy.py` | ✅ | +| Latência de notificação email | ≥ 1 admin notificado em ≤ 5min após `BanRequest` criado | Métrica celery `task_runtime` | 🟡 sem instrumentação formal | +| Decisões dentro de 7 dias | ≥ 90% dos `BanRequest` decididos em até 7 dias | Query `decided_at - created_at` | 🟡 sem dashboard | +| Cobertura de testes da hierarquia | 100% das 4 invariantes (I1/I2/I3/I4) com teste explícito | `pytest apps/moderation/tests/` | ✅ | + +--- + +## ADRs relacionadas (decisões locked-in) + +Detalhe completo em [`docs/specs/moderation/DESIGN.md §8`](../../specs/moderation/DESIGN.md). Destaques: + +- **ADR-006** (DevSecOps embedded) — fundamenta defesa em profundidade +- **ADR-010** (`/api/v1/` versionado) — prefixo das URLs +- **ADR-012** (`@transaction.atomic` em services que tocam ≥2 rows) — `ban_user()` atualiza `Ban` + `User.is_banned` na mesma transação +- **Sem ADR formal** para hierarquia `dev > admin > editor > user` — promover está em [DESIGN.md §9 Q4](../../specs/moderation/DESIGN.md) + +--- + +## Sprints envolvidas + +| Sprint | Escopo | Status | +| -------- | ----------------------------------------------------------- | ---------------------- | +| Sprint 2 | F-50 base — schema + service + views + permissões | ✅ entregue mai/2026 | +| Sprint 3 | F-50 hardening — defesa 3 camadas + testes I1/I2/I10 | ✅ entregue mai/2026 | +| Sprint 5 | Documentação retroativa (RF-003 + EP-05 + F-50 + DESIGN.md) | ✅ entregue 2026-06-09 | +| Sprint 8 | F-51 (notificação email do banido) — _previsto_ | ⏳ planejado | + +--- + +## Histórico de mudanças + +| Data | Evento | +| ------------------- | --------------------------------------------------------------------------------------------------------- | +| Sprint 2 (mai/2026) | Epic criado; F-50 implementada com schema final (`Ban` OneToOne + `BanRequest`); migration `0001_initial` | +| Sprint 3 (mai/2026) | Defesa em 3 camadas formalizada após observação 827 (29-mai) — Improvement-system §11.6 S8/C13 | +| 2026-06-09 | DESIGN.md retroativo escrito; EP-05 + F-50 + RF-003 preenchidos a partir de stubs | + +--- + +## Cross-references + +- [RF-003 — Moderação e banimento](../../requirements/RF/RF-003-moderation.md) +- [F-50 — Ban + BanRequest workflow](../features/F-50-ban-banrequest-workflow.md) +- [DESIGN.md retroativo de `moderation`](../../specs/moderation/DESIGN.md) +- [Improvement-system.md §11.6 S8 + C13](../../planning/Improvement-system.md) — origem da defesa em 3 camadas +- [CONCERNS.md](../../specs/codebase/CONCERNS.md) — débitos OPS-1/OPS-2/OPS-3 + GAP-1..GAP-6 +- [CLAUDE.md §4 Convenções](../../../CLAUDE.md) — hierarquia `dev > admin > editor > user` codificada + +--- + +_EP-05 ✅ Realizado em código Sprint 2-3 (pre-busca). Documentação retroativa concluída 2026-06-09. Próxima ação: F-51 (notificação email) no Sprint 8._ diff --git a/docs/backlog/epics/EP-06-administracao-sistema.md b/docs/backlog/epics/EP-06-administracao-sistema.md new file mode 100644 index 00000000..d6ad595b --- /dev/null +++ b/docs/backlog/epics/EP-06-administracao-sistema.md @@ -0,0 +1,127 @@ +# EP-06 — Administração do sistema + +> **Tipo**: Epic +> **Status**: 🚧 Em progresso (F-60 ✅ Done · F-62 🔴 obrigatória Sprint 5 · F-61/F-63 ⏳ Sprint 9+) +> **Prioridade global**: 🟠 Alta (F-62 escala para 🔴 Imediato — LGPD blocker) +> **Owner**: Gabriel Marques +> **Criado em**: Sprint 1 (retroativo formalizado 2026-06-09) + +--- + +## Visão de produto + +Este Epic agrupa o **substrato operacional do administrador e do desenvolvedor**: aquilo que faz o sistema **observável, auditável, monitorável e governável**. Não entrega valor direto para o leitor final — entrega valor para quem **opera** o Interpop (admin moderando, dev investigando incidente, DPO respondendo LGPD, monitor externo detectando downtime). + +Quatro capacidades de produto cabem aqui: + +1. **Trilha auditável de eventos sensíveis** — login, ban, publicação, mudança de senha, decisão de moderação ficam registrados em tabela INSERT-only, consultável por administrador para investigação ou cumprimento LGPD. +2. **Rastreabilidade end-to-end de requisição** — cada request HTTP recebe identificador único propagado em todos os logs, devolvido ao cliente no header `X-Request-ID`, permitindo correlacionar logs em segundos. +3. **Telemetria de erro com proteção de dados pessoais** — exceções não tratadas vão para Sentry com remoção automática de senha, token, e-mail, cookie e demais PII antes do envio. +4. **Monitoramento ativo de saúde + dashboard administrativo** — endpoint `/healthz/` consumido por UptimeRobot a cada 1min; dashboard `/api/v1/admin/metrics/` agrega KPIs para o admin. + +Trade-off honesto deste Epic: hoje, as quatro capacidades acima vivem **em um único app Django (`apps.audit`)** com responsabilidades grudadas — é o **maior débito estrutural do backend** (DESIGN §0 e CONCERNS §D-02). Este Epic cataloga a entrega atual (F-60), a obrigação regulatória que **bloqueia o go-live** (F-62 LGPD), e o caminho de saída para Sprint 9+ (F-61 refactor, F-63 admin promote/demote UI). **Não defende o desenho atual** — documenta para tornar o refactor visível e seguro. + +--- + +## KPI alvo do Epic + +| KPI | Alvo | Como medir | Status | +| ------------------------------ | ---------------------------------------- | ------------------------------------------------------- | ----------------------------- | +| Latência `/healthz/` | p99 ≤ 50ms | UptimeRobot histórico + assertion futura em test_health | 🟡 sem assertion (GAP-AUD-01) | +| Detecção de downtime | < 1 min do incidente | UptimeRobot 1×/min + alerta para owner | ✅ ativo | +| AuditLog cobertura | 100% dos POST/PUT/PATCH/DELETE elegíveis | Smoke test de eventos canônicos (login/ban/publish) | ✅ ativo via middleware | +| Conformidade LGPD AuditLog | retenção ≤ 2 anos, IP anonimizado ≥ 90d | Cron semanal + ADR de retenção | 🔴 **F-62 obrigatória** | +| `AdminMetricsView` query count | ≤ 25 queries (regression guard) | `assertNumQueries` em test_admin_metrics | 🟡 gap GAP-AUD-02 | +| Sentry quota | Eventos de healthz droppados em 100% | Sentry UI filter; bug atual com query string bypass | 🟠 D-AUD-01 a corrigir | + +--- + +## Requisitos realizados (rastreabilidade ↑) + +| ID | Requisito | Tipo | +| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------- | +| [RF-005](../../requirements/RF/RF-005-users-auth.md) | Autenticação + autorização (raiz das ações `login`/`logout`/`password_change` auditadas) | Funcional | +| [RF-006](../../requirements/RF/RF-006-audit.md) | Auditoria, observabilidade e telemetria operacional | Funcional | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Security headers, PII scrubbing, AuditLog INSERT-only, CSP roadmap | Segurança | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | `/healthz/` < 50ms p99 + UptimeRobot + rollback automático | Disponibilidade | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | AuditLog retenção + anonimização (S-10 hotfix) | LGPD | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | AdminMetrics tolera 1-2s; query budget ≤ 25 | Performance | + +--- + +## Features sob este Epic (rastreabilidade ↓) + +| ID | Nome | Sprint | Status | Arquivo | +| ---- | -------------------------------------------------------------------------------------- | ------ | ------------------------------ | ----------------------------------------------------- | +| F-60 | Observability + audit trail (4 responsabilidades grudadas em `apps.audit`) | 1 | ✅ Done (Sprint 1, pré-busca) | [F-60](../features/F-60-observability-audit-trail.md) | +| F-62 | AuditLog TTL + anonimização IP (LGPD S-10) | 5 | 🔴 **Obrigatória pré-go-live** | _a criar no kickoff Sprint 5_ | +| F-61 | Refactor `apps.audit` em 4 apps (observability + audit + admin_bff + security_headers) | 9+ | ⏳ Backlog | _a criar com ADR prévio_ | +| F-63 | Admin promote/demote role UI | 9+ | ⏳ Backlog | _a criar_ | + +**Nota sobre numeração**: a faixa `F-6X` é reservada para Features deste Epic. F-60 nasce com débito estrutural conhecido e mapeado; F-62 é hotfix regulatório que precede F-61 (não faz sentido refatorar app que ainda viola LGPD). + +--- + +## Débitos arquiteturais herdados (DESIGN §8) + +Este Epic carrega oito débitos do código em produção. Ordem de severidade: + +| # | Débito | Severidade | Feature/Sprint dona | +| -------- | ------------------------------------------------------------------ | ------------- | --------------------------- | +| S-10 | AuditLog sem TTL + IP cru — **LGPD blocker** | 🔴 Crítico | **F-62** (Sprint 5) | +| D-AUD-00 | 4 responsabilidades em um único app (`apps.audit`) | 🔴 Crítico | F-61 (Sprint 9+, ADR antes) | +| D-AUD-02 | `AdminMetricsView` ~25 queries sem cache/throttle/assertNumQueries | 🟠 Importante | F-60 → backlog interno | +| S-03 | CSP Report-Only indefinido + `CSP_REPORT_URI=''` silencioso | 🟠 Importante | F-60 (decisão pendente) | +| D-AUD-01 | Sentry `_before_send` filtro de healthz bypassed por query string | 🟠 Importante | F-60 (fix 2 linhas) | +| D-AUD-03 | RequestID e AuditLog middlewares divergem em defensive guard | 🟠 Importante | F-60 (fix 5 linhas) | +| D-AUD-04 | `target_repr` e `metadata` nasceram mortos no schema | 🟡 Menor | Decisão pendente Sprint 9+ | +| D-AUD-08 | `get_client_ip` não respeita `NUM_PROXIES` | 🟡 Menor | Backlog oportunístico | + +Cross-ref completo: [DESIGN.md §8](../../specs/audit/DESIGN.md), [CONCERNS.md](../../specs/codebase/CONCERNS.md). + +--- + +## ADRs relacionadas (decisões locked-in / pendentes) + +Em `docs/planning/Improvement-system.md` (gitignored — solicitar ao owner): + +- **A27** — Logging estruturado JSON com RequestContextFilter +- **A28** — Sentry init gating + PII scrubbing +- **A29** — `/healthz/` contract: sem auth, 2 checks, gate de deploy +- **A20** — Redis caching (libera D-AUD-06 — healthz de cache vira liveness real) +- **S-03 / S-09 §11.6** — Security headers (CSP, Permissions-Policy, HSTS) + +ADRs **pendentes** (DESIGN §10 open questions): + +- Split de `apps.audit` em 4 apps (ordem de extração, risco de quebrar ordem de middleware) +- AuditLog retention policy (90d? 1 ano? 2 anos?) — pré-requisito F-62 +- `action` cru vs. enum semântico (impacta DSAR e queries agregadas) +- `AdminMetricsView` BFF formal vs. split em `apps.admin_bff` +- CSP `enforce` timeline + plano para remover `script-src 'unsafe-inline'` + +--- + +## Sprints envolvidas + +| Sprint | Escopo | Status | Arquivo | +| --------- | ------------------------------------------------------------ | ------------------------ | -------------------------------------------------------------------------------------------- | +| Sprint 1 | F-60 (implementação inicial sem spec formal) | ✅ Entregue (retroativo) | _sem arquivo histórico de sprint formalizado_ | +| Sprint 5 | F-62 (LGPD hotfix) + débitos D-AUD-01/03 em paralelo | ⏳ Planejado | [sprint-5-filtros-deep-linking](../sprints/sprint-5-filtros-deep-linking.md) (compartilhado) | +| Sprint 9+ | F-61 (refactor 4 apps com ADR prévio) + F-63 (admin role UI) | ⏳ Backlog | _a criar_ | + +--- + +## Histórico de mudanças + +| Data | Evento | +| ---------- | ------------------------------------------------------------------------------------------------------------- | +| Sprint 1 | Implementação inicial do `apps.audit` (4 responsabilidades) sem spec formal — pré-Epic | +| 2026-06-09 | DESIGN retroativo do módulo (526 LOC) documenta débitos S-10/S-03/D-AUD-00..08 e gaps GAP-AUD-01..04 | +| 2026-06-09 | EP-06 formalizado retroativamente; substitui stub anterior; F-60 catalogada como ✅ Done Sprint 1 (pré-busca) | +| 2026-06-09 | F-62 escalada para 🔴 obrigatória Sprint 5 — bloqueia go-live regulatoriamente (LGPD Art. 16) | +| Sprint 5 | **Previsto**: F-62 entrega cron + ADR de retenção + endpoint CSP report | +| Sprint 9+ | **Previsto**: F-61 refactor com ADR prévio mandatório; F-63 admin promote/demote UI | + +--- + +_EP-06 formalizado retroativamente em 2026-06-09. Substitui stub anterior. **Anti-sycophancy**: este Epic carrega o maior débito estrutural do backend (DESIGN §0). Não defende o desenho atual — cataloga estado real, prioriza F-62 como blocker LGPD pré-go-live e mapeia F-61 (refactor) para Sprint 9+ com ADR obrigatório. Skills aplicadas: `engenharia-de-requisitos`, `tlc-spec-driven`, `architecture-decision-records`._ diff --git a/docs/backlog/epics/EP-10-busca-editorial.md b/docs/backlog/epics/EP-10-busca-editorial.md new file mode 100644 index 00000000..6a10fb57 --- /dev/null +++ b/docs/backlog/epics/EP-10-busca-editorial.md @@ -0,0 +1,94 @@ +# EP-10 — Busca editorial + +> **Tipo**: Epic +> **Status**: 🚧 Sprint 4 (F-30 ✅ Done · F-31/F-32 ⏳ Sprint 5) +> **Prioridade global**: 🟠 Alta +> **Owner**: Gabriel Marques +> **Criado em**: Sprint 4 (2026-06-02) · **Encerramento previsto**: Sprint 5 + +--- + +## Visão de produto + +Interpop é leitura longa. Quanto mais artigos publicamos, mais leitores se perdem tentando encontrar o que já leram OU descobrir um tema específico. Busca editorial resolve isso: leitor digita um termo, encontra artigos relevantes ordenados por **relevância** e **recência**, com possibilidade de filtrar por autor, editoria e intervalo de datas, e de compartilhar a busca via URL. + +KPI alvo pós-launch: + +- ≥ 40% dos leitores autenticados usam busca ao menos uma vez em 7 dias +- +15% em sessões com >2 páginas vistas +- p95 de tempo de resposta server ≤ 300ms em 50k artigos + +--- + +## Requisitos realizados (rastreabilidade ↑) + +Este Epic executa os seguintes requisitos: + +| ID | Requisito | Tipo | +| --------------------------------------------------------- | ------------------------------------------------------------ | -------------- | +| [RF-007](../../requirements/RF/RF-007-busca-editorial.md) | Sistema permite busca por texto livre nos artigos publicados | Funcional | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | p95 ≤ 300ms server, LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1 | Performance | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Throttle 30/60/500 min, HMAC cursor, XSS-safe highlight | Segurança | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | WCAG 2.2 AA em todos os 5 estados da busca | Acessibilidade | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | search_log retention 7d com pseudonimização | LGPD | + +--- + +## Features sob este Epic (rastreabilidade ↓) + +| ID | Nome | Sprint | Status | Arquivo | +| ---- | ----------------------------------- | ------ | ---------------------------------- | ---------------------------------------------- | +| F-30 | Busca por texto livre | 4 | ✅ Done (PR #37, commit `2bdf73b`) | [F-30](../features/F-30-busca-texto-livre.md) | +| F-31 | Filtros (autor, editoria, datas) | 5 | ⏳ Pending | [F-31](../features/F-31-filtros-busca.md) | +| F-32 | URL deep-linking + compartilhamento | 5 | ⏳ Pending | [F-32](../features/F-32-deep-linking-busca.md) | + +--- + +## Métricas de sucesso do Epic + +| Métrica | Alvo | Como medir | Status | +| ----------------------- | ------------------------------------- | ------------------------------------------ | ---------------------------- | +| Cobertura de descoberta | ≥ 40% MAU autenticado usa busca em 7d | Analytics (próximo Sprint) | ⏳ baseline pendente | +| Performance | p95 ≤ 300ms server em 50k artigos | k6 load test Zipfiano (T30.1.X22 Sprint 5) | ⏳ | +| Retenção | +15% sessões > 2 páginas | Comparação 30d pré/pós-launch | ⏳ | +| Acessibilidade | 0 violações axe-core nos 5 estados | CI gate `a11y.test.tsx` 12 cenários | ✅ | +| Bundle | Buscar lazy ≤ +20 KB gz | Lighthouse CI gate (TX-16 Sprint 5) | 🟡 manual hoje (14.54 KB gz) | + +--- + +## ADRs relacionadas (decisões locked-in) + +35 ADRs materializadas em [`docs/specs/busca-editorial/adrs/`](../../specs/busca-editorial/adrs/) — destaques por camada: + +- **Arquitetura**: ADR-015 (bounded context `apps.search`), ADR-016 (CQRS leve), ADR-017 (Service Layer) +- **DB**: ADR-018 (trigger SQL = SSOT), ADR-019 (CONFIGURATION pt_unaccent), ADR-021/021b (ts_rank_cd + recency 60d + CTE 500), ADR-039 (ENABLE ALWAYS) +- **Backend**: ADR-023 (URL `/api/v1/search/articles/`), ADR-024 (DRF throttle), ADR-025 (total_estimate EXPLAIN) +- **Frontend**: ADR-026 (CSR), ADR-027 (TanStack + useDebouncedValue), ADR-028 (``), ADR-030-FE (resilient sub-tree) +- **UI**: ADR-029 (paleta herdada), ADR-030-UI (chips radius-md + thumb-left) +- **Segurança**: ADR-035-039 (pseudonim. forte, throttle global, cache key, semgrep, trigger bypass test) +- **Testing**: ADR-040-045 (Hypothesis, schemathesis, Playwright visual, Stryker, k6, axe) + +--- + +## Sprints envolvidas + +| Sprint | Escopo | Status | Arquivo | +| -------- | -------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------- | +| Sprint 4 | F-30 (US30.1 + base de F-31/F-32) | ✅ entregue 2026-06-09 | [sprint-4-busca-editorial](../sprints/sprint-4-busca-editorial.md) | +| Sprint 5 | F-31 + F-32 + 11 tasks pendentes do REVIEW-PHASE-3 | ⏳ planejado | [sprint-5-filtros-deep-linking](../sprints/sprint-5-filtros-deep-linking.md) | + +--- + +## Histórico de mudanças + +| Data | Evento | +| ---------- | --------------------------------------------------------------------------- | +| 2026-06-02 | Epic criado em spec multi-agente (6 specialists + 2 validators + DESIGN v3) | +| 2026-06-03 | 35 ADRs materializadas + BACKLOG v5 | +| 2026-06-04 | F-30 implementada e revisada (Phases 1/2/3) | +| 2026-06-06 | F2-B-01/02/03 fechados; PR #37 ready | +| 2026-06-09 | PR #37 squash-merged em main como `2bdf73b`; F-30 ✅ Done | + +--- + +_Última atualização: 2026-06-09. Próxima ação: arrancar Sprint 5 com F-31/F-32._ diff --git a/docs/backlog/features/F-01-autenticacao-jwt-cookie-httponly.md b/docs/backlog/features/F-01-autenticacao-jwt-cookie-httponly.md new file mode 100644 index 00000000..feb12abc --- /dev/null +++ b/docs/backlog/features/F-01-autenticacao-jwt-cookie-httponly.md @@ -0,0 +1,402 @@ +# F-01 — Autenticação JWT em cookie httpOnly + +> **Tipo**: Feature +> **Epic pai**: [EP-01 Fundação da plataforma](../epics/EP-01-fundacao-plataforma.md) +> **Epic complementar**: [EP-06 Administração do sistema](../epics/EP-06-administracao-sistema.md) (hierarquia operada + comandos staff) +> **Sprint de execução**: Sprint 1 (pre-busca, anterior a Sprint 4) +> **Status**: ✅ Done em código · 🚧 Doc retroativa entregue 2026-06-09 com 5 Open Questions +> **Prioridade**: 🔴 Imediato (pré-condição para todo o produto editorial) + +--- + +## Descrição (visão de produto) + +Leitor entra no Interpop, cria conta com email + senha, e a partir daí o sistema o reconhece automaticamente entre visitas — sem pedir login a cada página, sem expor o token de sessão para JavaScript (proteção XSS), e renovando a credencial silenciosamente quando ela expira. Se esquecer a senha, recupera pelo email com um link de validade curta (1h); se trocar a senha, todas as outras sessões ativas em outros dispositivos são encerradas (padrão NYT/GitHub/Substack). Tentativas repetidas de login com senha errada bloqueiam temporariamente para conter ataques automatizados. Contas banidas têm o login recusado mesmo com a senha correta. + +O sistema também distingue 4 papéis editoriais (dono, admin, redator, leitor) com uma hierarquia estrita — só o dono pode banir admins; admins não banem outros admins; redatores só abrem pedidos de banimento; leitores comuns não banem ninguém. Esta hierarquia é **regra dura** e está implementada como função canônica única (`can_be_banned_by`) que toda permission do sistema chama. + +Esta Feature é **pré-condição** para tudo no produto: comentário, publicação, moderação, newsletter e auditoria dependem de identidade autenticada. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| [RF-005](../../requirements/RF/RF-005-users-auth.md) | Autenticação e autorização de usuários | **Realiza diretamente** (registro, login, rotação, recuperação, troca de senha) | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Argon2, cookie httpOnly+Secure+SameSite, brute-force lockout, throttle 10/min em auth | Realiza CA02, CA05, CA06, CA09 | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Mensagens neutras anti-enumeration; email em lowercase; pseudonimização em logs | Realiza CA08, CA13 | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Formulários de auth WCAG 2.2 AA: labels, foco, aria-live em erros, contraste | Realiza CA12 | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Falha de email (Celery) não derruba registro/login | Realiza CA10 | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | Login p95 ≤ 400ms (Argon2 deliberado); refresh p95 ≤ 100ms (sem hash) | Realiza CA11 | + +--- + +## Critérios de Aceitação (CAs) + +Derivados das invariantes I-01 a I-09 do [DESIGN.md §6](../../specs/users-auth/DESIGN.md). Cada CA é testável em booleano. + +| ID | Critério | Como verificar | Status | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------- | +| **CA01** | Email é armazenado em lowercase; cadastro com `Foo@Bar.com` e login com `foo@bar.com` resolvem para a mesma conta | Test `RegisterSerializer.validate_email` + login `iexact` | ✅ | +| **CA02** | Login só é aceito quando `is_active=True AND is_banned=False`; conta inativa ou banida recebe mensagem **neutra** (sem distinguir entre "não existe" e "banido") | Test `LoginSerializer.validate` 3 cenários | ✅ | +| **CA03** | Username é único `iexact` mas o case original do input é preservado no armazenamento | Test `RegisterSerializer.validate_username` | ✅ | +| **CA04** | Token JWT **nunca** sai no body de response — apenas em `Set-Cookie` com flags `HttpOnly`, `Secure`, `SameSite=Lax` | Test `LoginView` inspeciona response body + headers; lint regex em PRs | ✅ (verificar lint regex automatizado) | +| **CA05** | 5 falhas de login em 30 minutos na tupla `(ip, username)` bloqueiam novas tentativas por 30 minutos, **mesmo com credenciais corretas** | Test integração django-axes (mock IP) | ✅ | +| **CA06** | Throttle adicional de 10 req/min escala por IP em `/login/`, `/register/`, `/password-reset/` (defesa em profundidade) | Test `ScopedRateThrottle 'auth'` | ✅ | +| **CA07** | Mudança de senha (troca **ou** recuperação) blacklista **todos** os `OutstandingToken` não-expirados do usuário | Test `services.blacklist_all_user_tokens` + integração | ✅ | +| **CA08** | `POST /password-reset/` retorna sempre HTTP 200, independente de o email existir (anti-enumeration) | Test `PasswordResetRequestView` 2 cenários | ✅ | +| **CA09** | `PasswordResetToken` é one-shot: `is_used=True` após consumo; segunda tentativa com mesmo token é recusada com 400 | Test `PasswordResetConfirmSerializer.validate_token` 4 cenários | ✅ | +| **CA10** | `PasswordResetConfirmSerializer.save()` é `@transaction.atomic` — crash entre `set_password` e `is_used=True` deixa tudo desfeito | Test com `transaction.atomic` revert simulado | ✅ (ADR-012 / fix C3) | +| **CA11** | Rotação silenciosa: 401 em endpoint privado dispara refresh transparente; sessão dura 30 dias sem login manual | Test E2E + test unit `services.rotate_refresh_token` | ✅ (fix C1 §11.1) | +| **CA12** | Função canônica `can_be_banned_by(actor)` é a **única** fonte de verdade para decidir banimentos; nenhuma view duplica lógica role-by-role | Test matriz 4×4 (dev/admin/editor/user × alvo dev/admin/editor/user) | ✅ | +| **CA13** | Dono (`dev`) é imune a banimento por qualquer ator (inclusive outros dev); admin não bane outro admin (só dono); editor abre pedido (não bane direto) | Test matriz acima | ✅ | +| **CA14** | Formulários de login/registro/recuperação têm labels, foco visível, erro anunciado via `aria-live`, contraste ≥ 4.5:1 | Test `a11y.test.tsx` + WAVE manual | 🟡 axe-vitest parcial; E2E pendente | +| **CA15** | `PasswordResetToken` expira em 1h a partir de `created_at`; tentativa após expiração retorna 400 | Test com `freezegun` | ✅ | +| **CA16** | Logout invalida o refresh token no servidor (blacklist), não apenas apaga cookies | Test `LogoutView` + assert BlacklistedToken criado | ✅ | +| **CA17** | `IsNotBanned` em `DEFAULT_PERMISSION_CLASSES` impede acesso autenticado a usuário banido a qualquer endpoint privado padrão | Test integração: criar user banido, autenticar, hit `/me/`, esperar 403 | ✅ — risco S-06 latente quando view sobrescreve `permission_classes` | + +--- + +## User Stories + +### US01.1 — Leitor cria conta nova + +> **Como** visitante anônimo do Interpop +> **Quero** criar uma conta com meu email e senha +> **Para** poder comentar, curtir e receber a newsletter sob minha identidade. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 5 Story Points +- **Sprint**: 1 (pre-busca) +- **Status**: ✅ Done +- **CAs cobertos**: CA01, CA03, CA04, CA06, CA14 +- **Persona**: [visitante anônimo](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Cadastro de leitor + Como visitante anônimo + Quero criar uma conta + Para participar do Interpop como leitor identificado + +Cenário: Cadastro válido entrega sessão imediata (caminho feliz) + Dado que estou na página "/cadastro" como visitante anônimo + Quando preencho nome "Ana", sobrenome "Silva", email "Ana@Exemplo.com", apelido "ana_silva" e senha "S3nh@F0rte!" + E confirmo a senha + E envio o formulário + Então recebo a resposta com meus dados públicos (sem token no corpo) + E o navegador recebe os cookies "access_token" e "refresh_token" com flags HttpOnly e Secure + E meu email é armazenado como "ana@exemplo.com" (lowercase) + E meu apelido é armazenado como "ana_silva" (case preservado) + E eu sou redirecionada para a home já autenticada + +Cenário: Email já cadastrado é recusado com mensagem clara + Dado que existe uma conta com email "ana@exemplo.com" + Quando tento cadastrar nova conta com email "Ana@Exemplo.com" + Então vejo a mensagem "Este email já está em uso" + E o foco vai para o campo de email + E o erro é anunciado por leitores de tela via aria-live + +Cenário: Mais de 10 tentativas de cadastro em 1 minuto são bloqueadas + Dado que sou um visitante anônimo + Quando envio 11 requisições de cadastro em 60 segundos + Então a 11ª recebe HTTP 429 + E vejo a mensagem "Muitas tentativas. Aguarde Xs" + E o header Retry-After indica o tempo até liberação + +Cenário: Formulário é acessível por teclado e leitor de tela + Dado que estou na página "/cadastro" usando apenas teclado + Quando navego com Tab pelos campos + Então cada campo tem label visível associado + E o foco é visualmente destacado em cada elemento + E erros de validação são anunciados via aria-live="assertive" + E o contraste de texto/fundo é ≥ 4.5:1 em todos os estados (rest, focus, error) +``` + +--- + +### US01.2 — Leitor faz login com sessão persistente de 30 dias + +> **Como** leitor já cadastrado +> **Quero** entrar com meu email e senha uma vez +> **Para** continuar autenticado entre visitas sem digitar a senha novamente, com renovação silenciosa. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 8 Story Points +- **Sprint**: 1 (pre-busca) +- **Status**: ✅ Done (com fix C1 em rotação silenciosa) +- **CAs cobertos**: CA01, CA02, CA04, CA05, CA06, CA11, CA16, CA17 +- **Persona**: [leitor autenticado](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Login com sessão persistente + Como leitor já cadastrado + Quero entrar e permanecer autenticado + Para não digitar senha em toda visita + +Cenário: Login válido emite cookies httpOnly e sessão de 30 dias (caminho feliz) + Dado que existe conta ativa, não banida, com email "ana@exemplo.com" e senha "S3nh@F0rte!" + Quando envio POST "/api/v1/auth/login/" com {email: "Ana@Exemplo.com", password: "S3nh@F0rte!"} + Então recebo HTTP 200 com dados públicos (sem token no body) + E o cookie "access_token" tem flags HttpOnly, Secure, SameSite=Lax, validade 30 minutos + E o cookie "refresh_token" tem flags HttpOnly, Secure, SameSite=Lax, path="/api/v1/auth/refresh/", validade 30 dias + E meu campo "last_login" no banco é atualizado + +Cenário: Rotação silenciosa renova a sessão sem login manual + Dado que estou autenticado e meu access_token acabou de expirar (>30 minutos) + Quando o frontend faz GET "/api/v1/auth/me/" e recebe HTTP 401 + E o frontend chama POST "/api/v1/auth/refresh/" automaticamente + Então recebo HTTP 200 com novos cookies access_token e refresh_token + E o refresh_token antigo está blacklistado no servidor + E o frontend refaz o GET "/api/v1/auth/me/" com sucesso (HTTP 200) + E a transição é invisível para o leitor + +Cenário: Login após 5 falhas em 30 minutos é bloqueado mesmo com senha correta + Dado que falhei 5 vezes seguidas no login com email "ana@exemplo.com" no último 30 minutos + Quando tento entrar com a senha CORRETA + Então recebo HTTP 403 com mensagem de lockout + E o bloqueio dura 30 minutos + E o evento é registrado no AuditLog + +Cenário: Conta banida é recusada com mensagem neutra (anti-enumeration) + Dado que existe conta ativa porém banida com email "spammer@exemplo.com" + Quando envio POST "/api/v1/auth/login/" com credenciais corretas + Então recebo HTTP 401 com mensagem genérica "Credenciais inválidas" + E a mensagem NÃO distingue "conta não existe" de "conta banida" de "senha errada" + +Cenário: Logout invalida refresh no servidor (não só apaga cookie) + Dado que estou autenticado com refresh_token T1 + Quando envio POST "/api/v1/auth/logout/" + Então recebo HTTP 205 + E os cookies access_token e refresh_token são removidos do navegador + E o refresh_token T1 é blacklistado no servidor + E tentar usar T1 em POST "/api/v1/auth/refresh/" retorna HTTP 401 + +Cenário: Token JWT nunca aparece no body de response (XSS hardening) + Dado que faço qualquer request de auth (login, register, refresh, me) + Quando inspeciono o corpo da resposta + Então o corpo NÃO contém os caracteres "eyJ" (prefixo Base64 padrão de JWT) + E o corpo NÃO contém os campos "access" ou "refresh" como string + E tokens existem apenas em headers Set-Cookie +``` + +--- + +### US01.3 — Leitor recupera acesso quando esquece a senha + +> **Como** leitor que esqueceu a senha +> **Quero** redefinir minha senha via email +> **Para** voltar a entrar sem perder minha conta, com garantia de que sessões antigas em outros dispositivos sejam encerradas. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 5 Story Points +- **Sprint**: 1 (pre-busca) +- **Status**: ✅ Done (com fix C3 — atomicidade) +- **CAs cobertos**: CA07, CA08, CA09, CA10, CA15 +- **Persona**: [leitor autenticado em recovery](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Recuperação de senha por email + Como leitor que esqueceu a senha + Quero redefinir minha senha via link no email + Para recuperar acesso sem perder minha conta + +Cenário: Solicitação com email existente envia link de validade 1h (caminho feliz) + Dado que existe conta com email "ana@exemplo.com" + Quando envio POST "/api/v1/auth/password-reset/" com {email: "ana@exemplo.com"} + Então recebo HTTP 200 com mensagem "Se a conta existir, enviamos um email" + E uma tarefa Celery "send_password_reset_email" é enfileirada + E um PasswordResetToken é criado com expires_at = now + 1 hora + E todos os tokens anteriores não-usados desse usuário são invalidados (is_used=True) + +Cenário: Solicitação com email inexistente retorna 200 (anti-enumeration) + Dado que NÃO existe conta com email "fantasma@exemplo.com" + Quando envio POST "/api/v1/auth/password-reset/" com {email: "fantasma@exemplo.com"} + Então recebo HTTP 200 com a MESMA mensagem do cenário anterior + E NENHUMA tarefa Celery é enfileirada + E NENHUM PasswordResetToken é criado + E o tempo de resposta é estatisticamente indistinguível do cenário anterior (timing attack defense) + +Cenário: Confirmação com senha nova encerra todas as sessões em outros dispositivos + Dado que tenho 3 sessões ativas (laptop, celular, tablet) e recebi um PasswordResetToken válido + Quando envio POST "/api/v1/auth/password-reset/confirm/" com {token: "", new_password: "N0v@S3nh4!"} + Então recebo HTTP 200 + E minha senha é atualizada no banco (Argon2 hash) + E o token é marcado como is_used=True + E TODOS os OutstandingToken não-expirados das minhas 3 sessões são blacklistados + E a próxima request de qualquer um dos 3 dispositivos retorna 401 e força login + E TODA a operação está dentro de uma transação atomic + +Cenário: Token expirado é recusado com 400 + Dado que recebi um PasswordResetToken há 2 horas + Quando envio POST "/api/v1/auth/password-reset/confirm/" com esse token + senha nova + Então recebo HTTP 400 com mensagem "Token expirado ou inválido" + E minha senha NÃO é alterada + E o token permanece com is_used=False (não foi consumido) + +Cenário: Token já usado é recusado (one-shot) + Dado que consumi um PasswordResetToken com sucesso há 5 minutos (is_used=True) + Quando envio POST "/api/v1/auth/password-reset/confirm/" com o MESMO token e outra senha + Então recebo HTTP 400 com mensagem "Token expirado ou inválido" + E minha senha NÃO é alterada novamente + +Cenário: Formulário de recuperação é acessível + Dado que estou na página "/recuperar-senha" usando leitor de tela + Quando navego pelos campos com teclado + Então o campo de email tem label visível e descrição programática + E o estado de envio é anunciado via aria-live="polite" + E erros (token expirado, senha fraca) são anunciados via aria-live="assertive" + E o contraste de texto/fundo é ≥ 4.5:1 em todos os estados +``` + +--- + +## Tasks (implementação) + +Tasks foram executadas em **Sprint 1 (pre-busca)** — anteriores ao versionamento detalhado de commits que começou em Sprint 4. Marcadas como `✅ Done` sem hash específico; código vive em `backend/apps/users/`. + +### Tasks US-bound (T01.M.K) + +#### Para US01.1 (cadastro) + +| ID | Descrição | Prioridade | Status | Sprint | +| ------- | -------------------------------------------------------------------------------------------------------- | ---------- | ----------------------------- | ------ | +| T01.1.1 | Custom `User` UUID-PK herdando `AbstractBaseUser + PermissionsMixin` (sem `username` clássico do Django) | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.1.2 | `USERNAME_FIELD = 'email'`; email validado lowercase `iexact` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.1.3 | `RegisterSerializer` com regex `[A-Za-z0-9_.-]+` em `username`; case preservado, unicidade `iexact` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.1.4 | `RegisterView` emite tokens imediatos via `services.issue_tokens_for_user` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.1.5 | Migration inicial — `users_user` com índice composto `(role, is_active, is_banned)` | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | + +#### Para US01.2 (login + sessão + rotação) + +| ID | Descrição | Prioridade | Status | Sprint | +| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------- | ------ | +| T01.2.1 | `LoginSerializer.validate` chama `authenticate()` + assert `is_active and not is_banned` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.2.2 | `services.issue_tokens_for_user(user)` seta cookies `access_token` (30min, path=/) e `refresh_token` (30d, path=/api/v1/auth/refresh/) | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.2.3 | Flags de cookie: `HttpOnly=True`, `Secure=True` (False em dev override), `SameSite=Lax` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.2.4 | `services.rotate_refresh_token` — lê refresh do cookie, blacklista, emite par novo | 🔴 | ✅ Done (Sprint 1) + **fix C1** §11.1 | 1 | +| T01.2.5 | **Fix C1**: substituir `refresh.access_token.user` (atributo inexistente) por `User.objects.get(pk=refresh['user_id'])` + log `exc_info` ao invés de silenciar | 🔴 | ✅ Done (mid-Sprint, pre-busca) | 1 | +| T01.2.6 | `LogoutView` blacklista refresh + chama `services.logout_user` (clear cookies) | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.2.7 | `RefreshView` (`AllowAny`) lê refresh do cookie, blacklista, emite par novo | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.2.8 | `MeView` (GET retorna `UserPublicSerializer`; PATCH usa `UpdateProfileSerializer`) | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.2.9 | `ChangePasswordView` valida senha antiga → `services.blacklist_all_user_tokens` → `services.logout_user` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | + +#### Para US01.3 (recuperação de senha) + +| ID | Descrição | Prioridade | Status | Sprint | +| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------- | ------ | +| T01.3.1 | `PasswordResetToken` model — UUID token, `expires_at = created_at + 1h`, one-shot via `is_used` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.3.2 | `PasswordResetRequestView` — sempre HTTP 200 (anti-enumeration); enfileira `send_password_reset_email.delay()` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.3.3 | `PasswordResetConfirmSerializer.validate_token` — assert UUID + `is_valid` (not used and not expired) | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.3.4 | **Fix C3**: envolver `PasswordResetConfirmSerializer.save()` em `@transaction.atomic` — `set_password` + `is_used=True` + `blacklist_all_user_tokens` | 🔴 | ✅ Done (mid-Sprint, pre-busca) | 1 | +| T01.3.5 | Tarefa Celery `send_password_reset_email(email, token)` — template HTML + texto | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | + +#### Para hierarquia (compartilhada US01.2 / EP-06) + +| ID | Descrição | Prioridade | Status | Sprint | +| ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------------------------- | ------ | +| T01.4.1 | 4 roles em choices: `dev`, `admin`, `editor`, `user`; default = `user` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.4.2 | Properties canônicas em `User`: `is_admin` (admin OU dev), `can_publish` (dev/admin/editor) | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.4.3 | Função canônica `can_be_banned_by(actor)` — única fonte de verdade para hierarquia. Toda permission/view DEVE chamá-la (não duplicar lógica) | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.4.4 | `can_be_unbanned_by(actor)` espelha mesma regra — impede admin de desfazer ban que dev aplicou em outro admin | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.4.5 | DRF permissions: `IsAdminUser`, `IsAdminOrReadOnly`, `IsPublisherOrReadOnly`, `IsOwnerOrAdmin`, `IsNotBanned`, `IsEditorOrAdmin` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| T01.4.6 | `IsNotBanned` em `REST_FRAMEWORK.DEFAULT_PERMISSION_CLASSES` (defense in depth — risco S-06 quando view sobrescreve `permission_classes`) | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | + +### Tasks transversais (TX-NN) + +| ID | Descrição | Prioridade | Status | Sprint | +| ----- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------- | ------ | +| TX-01 | Configuração `SIMPLE_JWT` em `config/settings/base.py:132-159` (HS256, ACCESS=30min, REFRESH=30d, ROTATE=True, BLACKLIST_AFTER_ROTATION=True) | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| TX-02 | Argon2 como `PASSWORD_HASHERS[0]` em `base.py:106` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| TX-03 | django-axes settings: `AXES_FAILURE_LIMIT=5`, `AXES_COOLOFF_TIME=30min`, `AXES_LOCKOUT_PARAMETERS=['ip_address', 'username']` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| TX-04 | `ScopedRateThrottle 'auth': 10/min` em login/register/password-reset | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | +| TX-05 | Management command `seed_team_users` — cria dev/admin/editor staff idempotente lendo `.env` (escopo de EP-06 também) | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | +| TX-06 | URLs `/api/v1/auth/*` em `apps/users/urls.py:15-26`, montadas em `config/urls.py` | 🔴 | ✅ Done (Sprint 1, pre-busca) | 1 | +| TX-07 | Refresh cookie path-restrito a `/api/v1/auth/refresh/` (reduz superfície) | 🟠 | ✅ Done (Sprint 1, pre-busca) | 1 | +| TX-08 | **Hotfix S-02 (candidato)**: `JWT_SIGNING_KEY` hard-fail em prod replicando padrão `SEARCH_CURSOR_HMAC_SECRET` (commit `96cdad5`, ADR-035) | 🔴 | ⏳ **Pendente** — backlog imediato | TBD | + +### Tasks pendentes (housekeeping / hotfix) + +| ID | Descrição | Prioridade | +| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| T01.X1 | **Hotfix S-02**: `production.py` lê `JWT_SIGNING_KEY` obrigatório; `raise ImproperlyConfigured` se ausente OU igual a `SECRET_KEY` | 🔴 Imediato | +| T01.X2 | **Mitigação S-04**: fechar `/admin/` por firewall/WireGuard até 2FA TOTP entrar (decisão pendente: TOTP via `django-otp` ou WebAuthn) | 🔴 Imediato | +| T01.X3 | **Audit S-06**: enumerar todas as views privadas; assertar que `IsNotBanned` aparece explícito (não delega ao DEFAULT). Test de integração genérico | 🟠 Alta | +| T01.X4 | **Sessões pós-ban**: chamar `services.blacklist_all_user_tokens(banned_user)` no ato do ban — hoje sessão ativa sobrevive até access expirar (30min) | 🟠 Alta | +| T01.X5 | **LGPD DELETE account**: endpoint `DELETE /me/` com decisão anonimização vs hard-delete; tratar artigos/comentários órfãos | 🟡 Normal | +| T01.X6 | **Email verification flow** — adicionar campo `email_verified` + token de confirmação; cadastro hoje emite sessão sem verificar email | 🟡 Normal | +| T01.X7 | **Test E2E a11y completo** dos 3 formulários de auth (login, registro, recuperação) com `axe-playwright` nos estados rest/focus/error | 🟡 Normal | +| T01.X8 | `request.session.cycle_key()` pós-login (S-08 — session fixation latente se XSS chegar) | 🟡 Normal | +| T01.X9 | Decidir: remover `is_banned` de `UserPublicSerializer` público OU manter intencional (info-disclosure observação `860`) | ⚪ Baixa | + +--- + +## Definition of Done — verificação + +- [x] CA01–CA13, CA15, CA16 verificados por automated test +- [x] CA14 verificado parcialmente (axe-vitest nos componentes; E2E pendente — T01.X7) +- [x] CA17 verificado por test integração; risco S-06 latente identificado (T01.X3 audit pendente) +- [x] US01.1, US01.2, US01.3 com cenários BDD documentados +- [x] Todas as Tasks 🔴 Imediato (exceto TX-08 hotfix S-02) com status `✅ Done` em Sprint 1 +- [x] Fix C1 (rotação silenciosa) e Fix C3 (atomicidade reset) aplicados mid-Sprint 1 +- [x] Mergeada em `main` via Sprint 1 (pre-2026-05; sem PR # específico documentado) +- [x] Em produção no Hostinger KVM 1 desde Sprint 1 +- [ ] **2FA / TOTP para staff** — deliberadamente DEFERRED para backlog (S-04 crítico mas exige UX dedicada; mitigação imediata é firewall em `/admin/`) +- [ ] **Hotfix S-02 (JWT_SIGNING_KEY hard-fail)** — pendente; candidato a próxima ação não-Sprint-5 + +**Status final**: ✅ **Done em código** com 5 Open Questions abertas, 1 hotfix candidato (TX-08 / T01.X1) e 1 mitigação operacional crítica (T01.X2). + +--- + +## Specs técnicas relacionadas + +- [`docs/specs/users-auth/DESIGN.md`](../../specs/users-auth/DESIGN.md) — fonte de verdade técnica (responsabilidade, data model, public contract, fluxos críticos com diagramas Mermaid, invariantes, conhecimento operacional, status/débitos, open questions) +- [`docs/planning/session-auth-strategy.md`](../../planning/session-auth-strategy.md) (gitignored) — comparativo NYT/G1/BBC + roadmap TTL diferenciada por papel + step-up auth + multi-device session list. **Não duplicado** no DESIGN. +- [`docs/planning/Improvement-system.md`](../../planning/Improvement-system.md) §11.1 — histórico dos fixes C1 (rotação silenciosa) e C3 (atomicidade reset) + +--- + +## Open Questions (próximas decisões) + +Espelha [DESIGN.md §10](../../specs/users-auth/DESIGN.md). Bloco crítico que **deve** virar Sprint de housekeeping de auth: + +1. **S-04 — 2FA staff (CRÍTICO)**: TOTP via `django-otp` ou WebAuthn primeiro? Sem 2FA, a única barreira para `dev/admin/editor` é senha + axes. Mitigação imediata: T01.X2 (firewall em `/admin/`). +2. **S-02 — JWT_SIGNING_KEY fallback para SECRET_KEY**: vazamento de uma compromete a outra. **Candidato a hotfix** (T01.X1 / TX-08) replicando padrão F2-B-03 da busca (`96cdad5`). Esforço estimado: 1h. +3. **S-06 — IsNotBanned em DEFAULT_PERMISSION_CLASSES vaza por omissão**: DRF substitui (não merge) quando view declara `permission_classes`. Toda view privada nova precisa **repetir** `IsNotBanned`. **Audit pendente** (T01.X3). +4. **Sessões ativas pós-ban**: banimento impede login novo mas sessões ativas continuam até access expirar (≤30min). Aceitável para reader, problemático para staff. Fix: `blacklist_all_user_tokens(banned_user)` no ato do ban (T01.X4). +5. **DELETE account flow (LGPD)**: hoje só admin via Django admin. Decisão pendente: anonimização vs hard-delete; tratar artigos/comentários órfãos. (T01.X5) + +Adicionais (não-bloqueadores do MVP): + +6. **Email verification flow** — não existe; cadastro emite tokens imediatos (T01.X6). +7. **Social login (OAuth Google)** — sem ADR; demanda de produto bloqueada por decisão de identidade primária. +8. **Session invalidation em mudança de email** — `MeView PATCH` não invalida sessões hoje (mesmo padrão S7?). +9. **Endpoint API de promote/demote role** — hoje só management command + Django admin. +10. **TTL diferenciada por role** — `reader=60-90d / editor=14d / admin=4-8h` no roadmap de `session-auth-strategy.md`. + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-005](../../requirements/RF/RF-005-users-auth.md), [RNF-security](../../requirements/RNF/RNF-security.md), [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md), [RNF-a11y](../../requirements/RNF/RNF-a11y.md), [RNF-availability](../../requirements/RNF/RNF-availability.md), [RNF-perf](../../requirements/RNF/RNF-perf.md) | +| ↑ Epic pai | [EP-01 Fundação da plataforma](../epics/EP-01-fundacao-plataforma.md) | +| ↑ Epic complementar | [EP-06 Administração do sistema](../epics/EP-06-administracao-sistema.md) (ferramentas operadas de hierarquia + banimento + comandos staff) | +| → Specs técnicas | [users-auth/DESIGN.md](../../specs/users-auth/DESIGN.md) | +| → Sprint(s) | Sprint 1 (entrega original, pre-busca) · Sprint TBD (housekeeping / hotfix S-02 / 2FA) | +| → Features filhas | n/a (F-01 é Feature, não Epic) | +| ← Features irmãs sob EP-01 | F-02 (Django bootstrap), F-03 (Observability), F-04 (CI gates), F-05 (Frontend bootstrap) — 🚧 docs retroativas pendentes | +| → Consumido por (todos) | F-XX articles, F-XX comments, F-XX moderation, F-XX newsletter, F-XX audit, [F-30 busca](F-30-busca-texto-livre.md) | +| → ADRs | [ADR-008](../../planning/adrs/ADR-008-dpo-lgpd-baseline.md), [ADR-010](../../planning/adrs/ADR-010-api-v1-versioning.md), [ADR-012](../../planning/adrs/ADR-012-integridade-transacional.md), ADR-035 (padrão HMAC env-driven a replicar) | +| → CLAUDE.md | [§4 hierarquia `dev > admin > editor > user`](../../../CLAUDE.md) | + +--- + +_F-01 doc retroativa criada em 2026-06-09 (Skills aplicadas: `engenharia-de-requisitos`, `security-requirement-extraction`, `tlc-spec-driven`, `architecture-decision-records`). Código em `backend/apps/users/` desde Sprint 1. Próxima ação: aplicar hotfix S-02 (T01.X1) e definir janela de 2FA (S-04). Fonte de verdade técnica: [DESIGN.md](../../specs/users-auth/DESIGN.md)._ diff --git a/docs/backlog/features/F-10-publicacao-leitura-artigos.md b/docs/backlog/features/F-10-publicacao-leitura-artigos.md new file mode 100644 index 00000000..934c4665 --- /dev/null +++ b/docs/backlog/features/F-10-publicacao-leitura-artigos.md @@ -0,0 +1,416 @@ +# F-10 — Publicação e leitura de artigos + +> **Tipo**: Feature consolidada (4 sub-capacidades) +> **Epic pai**: [EP-02 Publicação editorial](../epics/EP-02-publicacao-editorial.md) +> **Sprint de execução**: Sprint 1-2 (pre-busca) — bootstrap + pivô editorial +> **Status**: ✅ Done em produção · 🚧 2 hotfix candidates ativos (OPS-1, D-10) +> **Prioridade**: 🔴 Imediato (núcleo editorial) + +--- + +## Descrição (visão de produto) + +Editor (papel `editor`, `admin` ou `dev`) cria rascunho de artigo preenchendo título, resumo, corpo de texto, imagem de capa com legenda obrigatória, e escolhe uma das 5 editorias canônicas (Música, Moda, Cinema, Literatura, Cultura Digital). Quando publica, o sistema carimba o momento da publicação, dispara notificação assíncrona para assinantes da newsletter, e o artigo passa a aparecer no listing público. + +Leitor (anônimo ou autenticado) acessa o artigo via URL pública `/noticia/`, com slug em português (`/noticia/a-nova-hegemonia-coreana-no-spotify`). Cada leitura conta como visualização, mas o mesmo IP só conta uma vez a cada 5 minutos para impedir inflação por F5. Quando alguém compartilha o link em WhatsApp/Twitter/Facebook/LinkedIn/Telegram/Discord/Slack, o robô do aplicativo recebe HTML enriquecido com Open Graph e Twitter Card — independente da SPA do navegador. + +Esta é a **Feature consolidada** que materializa todo o ciclo editorial. As 4 sub-capacidades cobertas (publicação, leitura, view_count, OG meta) são acopladas no app `apps.articles` por razões históricas — refactor para `apps/seo/` é débito OPS-2 documentado. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------ | +| [RF-001](../../requirements/RF/RF-001-articles.md) | Sistema permite publicação editorial + leitura via slug humano + OG meta + view_count anti-abuse | Realiza diretamente (todas as 5 subseções) | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | Listing público p95 ≤ 200ms; índices `(status, -published_at)` + `(author, status)` | Realiza CA12 | +| [RNF-security](../../requirements/RNF/RNF-security.md) | `IsPublisherOrReadOnly` + `IsOwnerOrAdmin` object-level; escape XSS no boundary | Realiza CA13 | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | WCAG 2.2 AA na página de artigo; landmarks; contraste em legenda | Realiza implícito (frontend) | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | IP do leitor usado apenas como chave de bucket (5 min TTL); não persistido | Realiza CA06 | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Crawler social tem path server-side dedicado; SPA quebrada não impede preview | Realiza CA07 | + +--- + +## Critérios de Aceitação (CAs) + +| ID | Critério | Como verificar | Status | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | +| **CA01** | Editor publica artigo com título, corpo de texto, autor, editoria, capa e legenda da capa (capa + legenda obrigatórias na criação) | `test_create_article_requires_cover_image_and_caption` | ✅ | +| **CA02** | Slug é gerado automaticamente a partir do título (kebab-case, único — sufixa `-1`, `-2` em caso de colisão), aceita acentuação portuguesa | DB constraint `unique=True` + `_unique_slug()` + conversor `` | ✅ | +| **CA03** | Mudança de status `draft → published` via API carimba `published_at = now()` automaticamente | `test_article_publish_triggers_send_article_notification_once` | 🟠 **violado via Django admin** (OPS-1 — hotfix candidato) | +| **CA04** | Artigo com `status=published` nunca tem `published_at NULL` (invariante I1) | Query `Article.objects.filter(status='published', published_at__isnull=True).count() == 0` | 🟠 violado por OPS-1 (mesma causa de CA03) | +| **CA05** | Leitor lê artigo via `/noticia/` em qualquer dispositivo; slug em português (`/noticia/o-novo-disco-da-céu`) funciona | Conversor `UnicodeSlugConverter` registrado em `apps.py:17` | ✅ | +| **CA06** | `view_count` incrementa apenas 1× por tupla (article, IP, janela 5 min) — bucket anti-abuse | `test_view_count_incremented_once_per_5min_window` | 🟡 **vaza entre workers gunicorn** (S-06 — Redis resolve) | +| **CA07** | Crawler social conhecido (Twitterbot, WhatsApp, facebookexternalhit, LinkedInBot, Slackbot, Discordbot, TelegramBot, Pinterest, redditbot, Applebot) recebe HTML com OG/Twitter Card meta — interceptado por `SocialOGMiddleware` em `/noticia/` | Manual `curl -A "WhatsApp/2.0" .../noticia//` | 🟡 **sem teste automatizado** (GAP-1) | +| **CA08** | Sistema oferece exatamente 5 editorias canônicas (Música, Moda, Cinema, Literatura, Cultura Digital) — data migration `0003` idempotente | Migration `0003_seed_pop_culture_categories.py` (linhas 1-19) | ✅ | +| **CA09** | Cada editoria tem cor e ícone próprios na UI (tokens `--clr-cat-*`) | Tokens em `src/styles/global.css` | ✅ | +| **CA10** | Imagem de capa tem fallback se ausente; serializer força `cover_image` + `cover_caption` obrigatórios na criação | `ArticleWriteSerializer.validate` linha 60 (`is_create = self.instance is None`) | ✅ | +| **CA11** | Apenas 1 artigo `is_featured=True` por vez (invariante I2) — setar como `True` desmarca todos os outros em uma única transação atômica | `test_marking_article_featured_unsets_previous`, `test_only_one_featured_after_multiple_marks` | ✅ | +| **CA12** | Ordenação default em listings: `-published_at, -created_at` | `Article.Meta.ordering` (`models.py:65`) | ✅ | +| **CA13** | Corpo de texto (`body`) é tratado como texto puro — frontend escapa via JSX (defesa em camada única, débito S-01 documentado) | Inspeção manual: zero `dangerouslySetInnerHTML` em `src/` | 🟡 **defesa única** (S-01 — quando `body` virar JSON estruturado, sanitização migra) | +| **CA14** | Anon **não vê** rascunhos; editorial autenticado (`user.can_publish`) vê drafts seus na listagem | `test_list_articles_anon_returns_only_published`, `_editor_sees_drafts`, `_reader_does_not_see_drafts` | ✅ | +| **CA15** | Editor X não edita/apaga artigo de editor Y via API — `IsOwnerOrAdmin` object-level garante | `test_editor_cannot_update_other_editors_article`, `_cannot_delete_` | ✅ | + +--- + +## User Stories + +### US10.1 — Editor publica artigo editorial (draft → published) + +> **Como** editor (papel `editor`, `admin` ou `dev`) +> **Quero** criar um rascunho de artigo e publicá-lo quando estiver pronto +> **Para** entregar conteúdo editorial revisado ao leitor e disparar notificação automática para a newsletter. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 13 Story Points (consolidada — bootstrap completo) +- **Sprint**: 1-2 (pre-busca) +- **Status**: ✅ Done +- **CAs cobertos**: CA01, CA02, CA03, CA04, CA10, CA11, CA13, CA14, CA15 +- **Persona**: [editor + admin + dev](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Publicação de artigo editorial + Como editor do Interpop + Quero criar e publicar artigos + Para entregar conteúdo revisado ao leitor + +Cenário: Editor cria rascunho com todos os campos obrigatórios + Dado que estou autenticado como editor "ana@interpop.com.br" + Quando envio POST para "/api/v1/articles/" com título, resumo, corpo, capa, legenda da capa e editoria "Música" + Então recebo 201 Created + E o artigo tem status="draft" + E o slug é gerado automaticamente a partir do título + E o campo published_at está vazio + E o autor do artigo é "ana@interpop.com.br" + +Cenário: Editor publica rascunho via API + Dado que existe um artigo draft de minha autoria com slug "sobre-o-bts" + Quando envio PATCH "/api/v1/articles/sobre-o-bts/" com {"status": "published"} + Então recebo 200 OK + E published_at é preenchido com o momento atual + E o signal post_save dispara send_article_notification.delay(article_id) + E o artigo aparece na listagem pública anônima + +Cenário: Editor publica via Django admin (BUG conhecido OPS-1) + Dado que existe um artigo draft de minha autoria + Quando publico pelo Django admin alterando status para "published" e salvando + Então o status fica como "published" + Mas published_at permanece NULL + E o artigo some da ordenação default ("-published_at, -created_at") + E o invariante I1 é violado + # Hotfix candidato Sprint 5: mover lógica de carimbo para Article.save() OU override ArticleAdmin.save_model + +Cenário: Editor X não edita artigo de editor Y + Dado que estou autenticado como editor "joao@interpop.com.br" + E existe um artigo de "ana@interpop.com.br" com slug "sobre-o-bts" + Quando envio PATCH "/api/v1/articles/sobre-o-bts/" com {"title": "Tentativa hostil"} + Então recebo 403 Forbidden + E o título original permanece inalterado + # IsOwnerOrAdmin object-level — admin e dev seguem podendo editar + +Cenário: Slug é único — sufixa em caso de colisão + Dado que já existe um artigo com slug "sobre-o-bts" + Quando crio outro artigo com título "Sobre o BTS" + Então o novo artigo recebe slug "sobre-o-bts-1" + +Cenário: Hero único — marcar como featured desmarca o anterior atomicamente + Dado que existe artigo A com is_featured=True + Quando salvo artigo B com is_featured=True + Então B fica com is_featured=True + E A fica com is_featured=False + E a transação é atômica (transaction.atomic) + +Cenário: Capa e legenda da capa são obrigatórias na criação + Dado que estou autenticado como editor + Quando envio POST "/api/v1/articles/" sem cover_image + Então recebo 400 Bad Request + E a resposta contém mensagem pt-BR "A imagem de capa é obrigatória" +``` + +--- + +### US10.2 — Leitor lê artigo via URL com slug humano + +> **Como** leitor (anônimo ou autenticado) +> **Quero** acessar o artigo via URL legível em português +> **Para** ler conteúdo editorial e compartilhar o link com colegas. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 5 Story Points +- **Sprint**: 1-2 (pre-busca) +- **Status**: ✅ Done +- **CAs cobertos**: CA05, CA06, CA12, CA14 +- **Persona**: [leitor anônimo + leitor autenticado](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Leitura de artigo via slug humano + Como leitor do Interpop + Quero acessar artigo por URL legível + Para ler e compartilhar conteúdo editorial + +Cenário: Anônimo lê artigo publicado via slug + Dado que existe artigo publicado com slug "a-nova-hegemonia-coreana" + Quando acesso "/noticia/a-nova-hegemonia-coreana" + Então vejo título, capa, legenda da capa, corpo e autor + E vejo a editoria com cor e ícone próprios + E o body aparece como texto puro (escapado via JSX) + +Cenário: Slug com acentuação portuguesa funciona + Dado que existe artigo publicado com slug "o-novo-disco-da-céu" + Quando acesso "/noticia/o-novo-disco-da-céu" + Então recebo 200 OK e vejo o artigo + # Conversor aceita \w unicode-aware + +Cenário: Anônimo NÃO vê rascunho + Dado que existe artigo com status="draft" + Quando o anônimo lista "/api/v1/articles/" + Então o draft NÃO aparece na resposta + # IsPublisherOrReadOnly + filter status='published' para anon + +Cenário: Editor autenticado vê drafts seus na listagem + Dado que estou autenticado como editor com 2 drafts e 1 published + Quando faço GET "/api/v1/articles/" + Então a resposta contém os 3 artigos (incluindo drafts) + # user.can_publish controla o boundary + +Cenário: View_count incrementa 1× por (article, IP, 5min) + Dado que existe artigo publicado com slug "sobre-o-bts" e view_count=10 + Quando o IP 192.0.2.1 envia POST "/api/v1/articles/sobre-o-bts/view/" 5× em 1 minuto + Então view_count fica 11 (não 15) + E o bucket "view_count:sobre-o-bts:192.0.2.1" expira em 5 minutos + # Em prod com 3 workers gunicorn: vaza para 13 (S-06 — Redis A20 resolve) + +Cenário: View_count NÃO incrementa em artigo draft + Dado que existe artigo draft + Quando envio POST "/api/v1/articles//view/" + Então recebo 204 No Content + Mas view_count permanece em 0 + # Invariante I8: filter status='published' na query de UPDATE + +Cenário: Ordenação default privilegia publicação recente + Dado que existem 3 artigos publicados em datas diferentes + Quando faço GET "/api/v1/articles/" + Então a ordem é "-published_at, -created_at" (mais recente primeiro) +``` + +--- + +### US10.3 — Crawler social recebe cartão rico com OG meta + +> **Como** robô de rede social (Twitterbot, WhatsApp, facebookexternalhit, LinkedInBot, Slackbot, Discordbot, TelegramBot, Pinterest, redditbot, Applebot) +> **Quero** receber HTML server-side com Open Graph e Twitter Card preenchidos +> **Para** mostrar prévia (cartão) com título, capa, autor e resumo quando o leitor compartilhar o link. + +- **Prioridade**: 🟠 Alta (60-80% do tráfego vem de redes sociais) +- **Estimativa**: 5 Story Points +- **Sprint**: 2 (entrou junto com pivô editorial) +- **Status**: ✅ Done · 🟡 sem teste automatizado (GAP-1) +- **CAs cobertos**: CA07 +- **Persona**: crawler social (não-humano) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Cartão social rico via SocialOGMiddleware + Como robô de rede social + Quero receber HTML server-side com OG meta + Para renderizar prévia correta do artigo + +Cenário: WhatsApp recebe HTML com OG meta + Dado que existe artigo publicado com slug "sobre-o-bts" + Quando o User-Agent "WhatsApp/2.0" faz GET "/noticia/sobre-o-bts/" + Então recebo 200 text/html com meta tags OG + E vejo og:title= + E vejo og:description= + E vejo og:image= + E vejo og:url=/noticia/sobre-o-bts/ + E vejo twitter:card=summary_large_image + +Cenário: Crawler em artigo draft recebe 404 + Dado que existe artigo draft com slug "rascunho-vazado" + Quando "facebookexternalhit/1.1" faz GET "/noticia/rascunho-vazado/" + Então recebo 404 + # Middleware filtra status='published' explicitamente + +Cenário: Navegador comum NÃO é interceptado + Dado o mesmo artigo publicado + Quando User-Agent "Mozilla/5.0" (humano) faz GET "/noticia/sobre-o-bts/" + Então a requisição vai direto para o SPA (não passa pelo middleware OG) + # _CRAWLER_RE só casa com 10 user agents conhecidos (case-insensitive) + +Cenário: SITE_URL ausente em produção falha cedo (RNF — anti-OPS-3) + Dado que SITE_URL não está definido em produção + Quando o backend inicializa + Então o boot falha com ImproperlyConfigured + # Hoje: fallback localhost:5173 hardcoded — OPS-3 ativo +``` + +--- + +### US10.4 — Editor categoriza artigo em uma das 5 editorias canônicas + +> **Como** editor +> **Quero** classificar o artigo em uma das 5 editorias canônicas (Música, Moda, Cinema, Literatura, Cultura Digital) +> **Para** sustentar a identidade taxonômica da marca e permitir filtragem pelo leitor. + +- **Prioridade**: 🟠 Alta +- **Estimativa**: 3 Story Points +- **Sprint**: 2 (pivô editorial) +- **Status**: ✅ Done +- **CAs cobertos**: CA08, CA09 +- **Persona**: editor + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Categorização editorial em 5 editorias fixas + Como editor do Interpop + Quero escolher entre 5 editorias canônicas + Para classificar o artigo dentro da identidade da marca + +Cenário: Sistema oferece exatamente 5 editorias canônicas após migrations + Dado que as migrations 0001-0003 foram aplicadas + Quando consulto Category.objects.all().order_by('name') + Então vejo exatamente: Cinema, Cultura Digital, Literatura, Moda, Música + E nenhuma categoria legada (Política, Tecnologia, Cultura, Negócios, Internacional, Economia) sobrevive vazia + +Cenário: Apagar categoria deixa artigo órfão (rótulo, não conteúdo) + Dado que existe artigo na editoria "Música" + Quando a categoria "Música" é apagada + Então o artigo permanece (category=NULL via SET_NULL) + # Invariante I10: conteúdo sobrevive sem categoria + +Cenário: API NÃO permite criar categoria nova + Dado que estou autenticado como editor ou admin + Quando tento POST "/api/v1/categories/" com {"name": "Esportes"} + Então recebo 405 Method Not Allowed + # Vocabulário só muda via data migration OU Django admin (ADR-002) + +Cenário: Editor escolhe editoria ao criar artigo + Dado que estou autenticado como editor + Quando crio artigo com category_id= + Então o artigo é salvo com category="Cinema" + E na resposta vejo o nome e slug da categoria nested + +Cenário: Filtrar listagem por editoria + Quando faço GET "/api/v1/articles/?category__slug=cinema&status=published" + Então recebo apenas artigos publicados da editoria Cinema +``` + +--- + +## Tasks (implementação — retroativas Sprint 1-2, pre-busca) + +### Tasks US-bound (T10.X — todas ✅ Done — Sprint 1-2, pre-busca) + +| ID | Descrição | Prioridade | Sprint | Nota | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------ | ------------------------------------------------------------------------------------------------ | +| T10.1.1 | Bootstrap `apps.articles` (apps.py, urls.py, admin.py, models.py base) | 🔴 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.2 | Model `Article` (UUID PK, title, slug unique, excerpt, body, cover_image, author PROTECT, status) | 🔴 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.3 | Model `Category` (name unique, slug unique, ordering by name) | 🔴 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.4 | Migrations `0001_initial` + `0002_initial` — schema base | 🔴 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.5 | `Article._unique_slug()` — sufixa `-1`, `-2` em caso de colisão | 🟠 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.6 | `Article.save()` override com `transaction.atomic` para invariante I2 (único featured) | 🟠 | 1 | ✅ Done (Sprint 1-2, pre-busca) — ADR-012 | +| T10.1.7 | `Category.save()` override com `slugify(name, allow_unicode=True)` | 🟡 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.8 | `ArticleWriteSerializer` — `cover_image` + `cover_caption` obrigatórios na criação | 🟠 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.9 | `ArticleListSerializer` (plano, sem body) + `ArticleDetailSerializer` (com body + cover_caption) | 🟠 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.10 | `ArticleListView` + `ArticleDetailView` (DRF generic views) com filtros `category__slug`, `status`, `is_featured`, busca `?search=`, ordering | 🟠 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.11 | `CategoryListView` (read-only, AllowAny — 5 itens fixos) | 🟡 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.12 | URLs `/api/v1/articles/` + `/api/v1/articles//` + `/api/v1/categories/` (prefixo ADR-010) | 🟠 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.13 | `ArticleDetailView.perform_create/update` carimba `published_at = now()` em transição draft → published | 🔴 | 1 | ✅ Done (Sprint 1-2, pre-busca) — **causa de OPS-1** (não roda via admin) | +| T10.1.14 | Permissions: `IsPublisherOrReadOnly` (view-level) + `IsOwnerOrAdmin` (object-level — editor X não toca artigo de editor Y) | 🔴 | 1 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.15 | Signal `pre_save` — snapshot `_prev_status` (anti-double-fire de newsletter) | 🟠 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.1.16 | Signal `post_save` — dispara `send_article_notification.delay(article_id)` (lazy import anti-circular) | 🟠 | 2 | ✅ Done — refactor Sprint 4 (C12: Celery `.delay()` substituiu chamada síncrona) | +| T10.1.17 | `ArticleAdmin` Django — `list_display`, `list_filter`, `search_fields`, `prepopulated_fields={'slug':('title',)}`, action `resend_notification` | 🟡 | 1 | ✅ Done (Sprint 1-2, pre-busca) — **OPS-1 origina aqui** (admin não chama perform_create/update) | +| T10.1.18 | Migration `0003_seed_pop_culture_categories` — idempotente via `get_or_create`; remove 6 legadas se vazias | 🔴 | 2 | ✅ Done (Sprint 1-2, pre-busca) — pivô editorial | +| T10.1.19 | Migration `0004_article_cover_caption` — `CharField(300)` blank/default | 🟡 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.2.1 | `ArticleViewCountView` (POST `/api/v1/articles//view/`) — bucket anti-abuse `cache.add(key, True, 300)` | 🟠 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.2.2 | Atomic update `Article.objects.filter(slug=..., status='published').update(view_count=F('view_count')+1)` | 🟠 | 2 | ✅ Done (Sprint 1-2, pre-busca) — invariante I8 | +| T10.2.3 | `apps.audit.get_client_ip` — extração X-Forwarded-For única no projeto (C13) | 🟠 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.3.1 | `og_middleware.py` — `SocialOGMiddleware` com regex `_CRAWLER_RE` (10 user agents) | 🟠 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.3.2 | `_render_og_html(article)` — gera HTML mínimo com OG/Twitter Card meta; escape via `_escape` custom (débito S-11) | 🟡 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.3.3 | `sitemaps.py` — sitemap.xml manual (SimplerXMLGenerator) apontando para `SITE_URL` (frontend), não backend | 🟡 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.3.4 | `robots_view.py` — robots.txt dinâmico com `Disallow: /api/`, `/django-admin/` + linha Sitemap | 🟡 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.3.5 | `converters.py` — `UnicodeSlugConverter` registrado como `` global em `ready()` (anti-`RemovedInDjango60Warning`) | 🟠 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.4.1 | `Category` admin com `prepopulated_fields={'slug':('name',)}` (criação manual rápida) | 🟡 | 2 | ✅ Done (Sprint 1-2, pre-busca) | + +### Tasks de teste (baseline retroativa — Sprint 1-2, pre-busca) + +| ID | Descrição | Prioridade | Sprint | Status | +| ------- | -------------------------------------------------------------------------------------------------- | ---------- | ------ | ------------------------------- | +| T10.5.1 | `test_views.py` — 21+ testes cobrindo CRUD, permissões, filtros, featured-único, view_count | 🔴 | 1-2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.5.2 | `test_article_publish_triggers_send_article_notification_once` — invariante I1 + dispatcher Celery | 🔴 | 4 | ✅ Done (refactor Celery C12) | +| T10.5.3 | `test_view_count_incremented_once_per_5min_window` — bucket anti-abuse (I4) | 🟠 | 2 | ✅ Done (Sprint 1-2, pre-busca) | +| T10.5.4 | `test_only_one_featured_after_multiple_marks` — invariante I2 (atomic) | 🟠 | 1 | ✅ Done (Sprint 1-2, pre-busca) | + +### Tasks transversais (TX-NN — configurações técnicas e infraestrutura cruzada) + +| ID | Descrição | Prioridade | Quando | +| ----- | ----------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------- | +| TX-01 | Configurar `MEDIA_URL` + `MEDIA_ROOT` para `cover_image` upload (`media/covers/%Y/%m/`) | 🔴 | ✅ Done (Sprint 1-2, pre-busca) | +| TX-02 | Wire de `MIDDLEWARE` em `config/settings/base.py` (`SocialOGMiddleware` na ordem correta) | 🟠 | ✅ Done (Sprint 1-2, pre-busca) | +| TX-03 | Wire de URLs globais para `/sitemap.xml` + `/robots.txt` + `/noticia//` | 🟠 | ✅ Done (Sprint 1-2, pre-busca) | +| TX-04 | Configurar `SITE_URL` em `.env` (fallback `localhost:5173` hardcoded — débito OPS-3) | 🟠 | ✅ Done (Sprint 1-2, pre-busca) — **débito OPS-3 ativo** | + +--- + +## Definition of Done — verificação + +- [x] CA01, CA02, CA05, CA08, CA09, CA10, CA11, CA12, CA14, CA15 verificados por test automatizado +- [ ] **CA03, CA04 violados em produção via Django admin** (OPS-1 — hotfix candidato Sprint 5) +- [x] CA06 ok em dev/testes; 🟡 vaza entre workers gunicorn em prod (S-06 — A20 Redis resolve) +- [ ] CA07 sem teste automatizado (GAP-1 — Sprint de hardening de testes) +- [x] CA13 mantido por escape JSX (defesa única — débito S-01 documentado) +- [x] US10.1, US10.2, US10.3, US10.4 com cenários BDD rodando verde (`apps/articles/tests/test_views.py` 21+ testes) +- [x] Todas as Tasks Imediate/Alta done com referência ao código +- [x] Code-review implícito (módulo em produção desde Sprint 2) +- [x] Documentação cruzada atualizada — RF-001 + EP-02 + [DESIGN.md](../../specs/articles/DESIGN.md) citam esta Feature +- [x] Em produção desde Sprint 2 (sem hotfix de regressão até 2026-06-09) + +**Status final**: ✅ **Done** com 2 hotfix candidates (OPS-1 admin published_at + D-10 cover URL relativa em email) e 1 GAP de teste (GAP-1 crawler social). Não bloqueia evolução do produto, mas justifica Sprint de housekeeping. + +--- + +## Open Questions (para futuro DESIGN evolutivo) + +1. **OPS-1 — Publicar via Django admin não preenche `published_at`** (artigos somem da ordenação default). Hotfix: mover lógica de carimbo do `views.py:64,87` para `Article.save()` OU override `ArticleAdmin.save_model`. Prefira o segundo: mais cirúrgico, não acopla model a regra de boundary. **Sprint 5 candidato.** +2. **S-06 — `view_count` bucket vaza entre workers gunicorn** (LocMemCache per-process; 3 workers = 3× o limite por IP). Resolve com Redis distribuído (A20). Sem isso, ranking "mais lidos" infla artificialmente. +3. **ADR-002 — Tags livres entram quando?** Vocabulário fixo é decisão deliberada, mas re-avaliar quando volume passar de ~500 artigos publicados. Métrica de gatilho: editor pedindo classificação fora das 5 categorias > 5×/mês. +4. **ADR-014 — `body` JSONField estruturado entra quando?** Quando time editorial demandar blocos tipados (citação, embed de tweet/Instagram, mídia). Migração: dual-write durante 2 sprints, depois flip de leitura, depois drop do `body` texto puro. +5. **OPS-2 — Refactor SEO para `apps/seo/`?** Extrair `sitemap.py` + `robots_view.py` + `og_middleware.py` + conversor. Custo: tocar `MIDDLEWARE` em `base.py` + URLs globais + dependência circular com Article. Benefício: SRP + reuso do middleware em outros tipos de página (Sobre, Newsletter landing). +6. **Soft-delete em Article?** Padrão NYT/Folha (never delete) vs LGPD (apagar = remover dado pessoal de leitor). Decisão depende de pedido editorial. Re-avaliar a partir de 200 publicados. +7. **OG meta — sem teste automatizado (GAP-1)**. Test client + `HTTP_USER_AGENT='WhatsApp/2.0'` cobre invariante I5 em 5 LoC. Por que não temos? Sprint 1-2 priorizou cobertura de domínio sobre middleware. +8. **D-10 — newsletter envia link de capa quebrado** (relativo em email). Hotfix: usar `SITE_URL + article.cover_image.url` em template de email. Sprint 5 candidato (junto com OPS-1). + +--- + +## Specs técnicas relacionadas + +- [DESIGN.md](../../specs/articles/DESIGN.md) — spec retroativo completo (fonte de verdade técnica) +- [CONCERNS.md](../../specs/codebase/CONCERNS.md) — débitos S-01, S-06, S-11, D-07, D-10, OPS-1..6, GAP-1..4 +- [ARCHITECTURE.md](../../specs/codebase/ARCHITECTURE.md) — grafo de apps (Ca=4 / Ce=3 / I=0.43) +- [CONVENTIONS.md](../../specs/codebase/CONVENTIONS.md) — permissions, slug, UUID +- [STRUCTURE.md](../../specs/codebase/STRUCTURE.md) — `backend/apps/articles/` +- [Improvement-system.md](../../planning/Improvement-system.md) — backlog mestre pré-reorg + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-001](../../requirements/RF/RF-001-articles.md), [RNF-perf](../../requirements/RNF/RNF-perf.md), [RNF-security](../../requirements/RNF/RNF-security.md), [RNF-a11y](../../requirements/RNF/RNF-a11y.md), [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md), [RNF-availability](../../requirements/RNF/RNF-availability.md) | +| ↑ Epic pai | [EP-02](../epics/EP-02-publicacao-editorial.md) | +| → Sprint(s) | Sprint 1 (bootstrap), Sprint 2 (pivô editorial + middleware OG) | +| → Specs técnicas | [DESIGN.md retroativo](../../specs/articles/DESIGN.md) + ADRs 002/009/010/012/014/018 | +| → Features filhas | n/a (F-10 consolida — não é Epic) | +| → Features que dependem | [F-30 Busca por texto livre](F-30-busca-texto-livre.md) (read-projection via trigger PL/pgSQL sobre Article — ADR-018) | +| ← Features irmãs sob EP-02 | (única Feature do Epic — granularidade deliberada) | + +--- + +_F-10 ✅ Done desde Sprint 2 (2026 H1, pre-busca). Formalizada retroativamente em 2026-06-09. **Hotfix candidate Sprint 5**: OPS-1 (admin published_at NULL) — bug latente que viola invariante I1 e some artigos da ordenação default. Custo de fix: ~30 LoC + 1 teste. Impacto: alto (qualquer artigo publicado pelo admin Django fica "invisível" no listing default)._ diff --git a/docs/backlog/features/F-20-comentarios-e-curtidas.md b/docs/backlog/features/F-20-comentarios-e-curtidas.md new file mode 100644 index 00000000..9ccfd970 --- /dev/null +++ b/docs/backlog/features/F-20-comentarios-e-curtidas.md @@ -0,0 +1,330 @@ +# F-20 — Comentários e curtidas + +> **Tipo**: Feature +> **Epic pai**: [EP-03 Engajamento da comunidade](../epics/EP-03-engajamento-comunidade.md) +> **Sprint de execução**: Sprint 2-3 (pré-formalização — documentação retroativa em 2026-06-09) +> **Status**: ✅ Done em código (Sprint 2-3, pre-busca) +> **Prioridade**: 🟠 Alta (espinhaço de engajamento editorial) + +--- + +## Descrição (visão de produto) + +Leitor autenticado entra no artigo, lê, e pode **comentar** abaixo. Outros leitores podem **responder** ao comentário (uma única vez na árvore — o Interpop corta aninhamento em 1 nível para preservar legibilidade editorial). Qualquer leitor pode **curtir** comentários alheios para sinalizar concordância sem precisar escrever. Quem se arrepende **remove** o próprio comentário; o conteúdo desaparece da tela, mas a linha permanece no banco para auditoria. Admin pode remover comentário de qualquer leitor (moderação reativa); editor não tem esse poder. + +Leitor **banido** perde acesso de escrita (POST de comentário, POST de like, DELETE) por defesa em profundidade — banimento não esconde comentários antigos, apenas trava ações futuras. + +A leitura pública (GET) é aberta — anônimo enxerga tudo, mas não escreve nada. Comentários em artigo `draft` são invisíveis e bloqueados para escrita. + +Esta Feature é a **fundação** do engajamento. Features futuras (F-21 notificações, F-22 anti-spam) constroem em cima dela. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------- | +| [RF-002](../../requirements/RF/RF-002-comments.md) | Sistema permite leitor autenticado comentar, responder, curtir e remover | Realiza diretamente | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Auth obrigatória, defesa em profundidade `IsNotBanned`, throttle `comments_create` é débito | Realiza CA04, CA12 (parcial) | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Form e botões acessíveis por teclado | Realiza CA13 | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Soft-delete preserva audit trail; cleanup é débito (OPS-2) | Realiza CA06 (parcial) | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Falha de `apps.comments` não derruba leitura do artigo | Realiza implicitamente | + +--- + +## Critérios de Aceitação (CAs) + +| ID | Critério | Como verificar | Status | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------- | +| **CA01** | Leitor autenticado e não banido cria comentário top-level em artigo publicado | `test_create_comment_authenticated_returns_201` (`test_views.py`) | ✅ | +| **CA02** | Leitor responde a outro comentário do mesmo artigo via `parent_id` no payload | `test_create_reply_with_valid_parent_returns_201` | ✅ | +| **CA03** | Apenas 1 nível de aninhamento — resposta a uma resposta retorna 400 ("Comentário pai inválido") | `validate_parent_id` filtra `parent=None` (`serializers.py:48`) | 🟡 GAP-1 (sem teste direto da invariante I8) | +| **CA04** | Leitor banido recebe 403 ao tentar criar, responder, curtir ou remover (defesa em profundidade `IsNotBanned`) | `test_create_comment_banned_user_returns_403` (`test_views.py:123`) | ✅ | +| **CA05** | Leitor remove seu **próprio** comentário (`IsOwnerOrAdmin`); admin remove de qualquer leitor; editor não remove de terceiros | `test_delete_other_users_comment_returns_403` (`test_views.py:224`) | ✅ | +| **CA06** | Remoção é lógica (soft-delete): `is_deleted=True`, `deleted_at=now`, `deleted_by=user`; conteúdo some do display público; linha permanece | `test_delete_own_comment_soft_deletes` (`test_views.py:207`) | ✅ | +| **CA07** | Comentários soft-deletados não aparecem em listagem pública (`filter(is_deleted=False)` em `views.py:38`) | `test_list_comments_hides_soft_deleted` (`test_views.py:67`) | ✅ | +| **CA08** | Replies a comentário soft-deletado ficam órfãos no DB (parent some da API; reply sobrevive sem aparecer como top-level) | _Comportamento atual sem decisão de UX_ — débito OPS-1 | 🟡 ambíguo (ver Open Questions) | +| **CA09** | Leitor curte comentário (POST `/api/v1/comments//like/`); idempotente via `unique_together('comment','user')` | `test_like_comment_creates_like_returns_200` | ✅ | +| **CA10** | Leitor descurte ao chamar o mesmo endpoint novamente (toggle); resposta retorna `{liked: false, likes_count: N}` | `test_like_twice_unlikes_returns_200` | ✅ | +| **CA11** | `likes_count` retornado == `count(CommentLike WHERE comment=X)` (invariante de integridade) | `test_like_count_correct_with_multiple_users` (`test_views.py:287`) | ✅ | +| **CA12** | Throttle específico `comments_create` protege contra flood em artigo viral | _Sem `ScopedRateThrottle` configurado_ — débito **S-07** (hotfix candidate) | ❌ **GAP DE SEGURANÇA** | +| **CA13** | Form de comentário, botão de curtida e estado "removido" acessíveis por teclado e leitor de tela | Manual via WAVE/axe (não há `a11y.test.tsx` para `apps.comments`) | 🟡 manual, sem CI gate | +| **CA14** | Comentários em artigo `draft` são invisíveis (GET retorna 404) e bloqueados para escrita (POST retorna 404) | `test_list_comments_404_for_draft_article` (`test_views.py:81`) | ✅ | +| **CA15** | Soft-delete preserva `id` + `created_at` + `author` (audit trail íntegro mesmo após remoção) | `test_delete_own_comment_preserves_metadata` (derivado de `test_views.py:207`) | ✅ | + +--- + +## User Stories + +### US20.1 — Leitor autenticado comenta artigo + +> **Como** leitor autenticado e não banido +> **Quero** comentar um artigo publicado +> **Para** registrar minha leitura, discordar, complementar ou corrigir publicamente. + +- **Prioridade**: 🟠 Alta +- **Estimativa**: 5 Story Points +- **Sprint**: 2-3 (retroativo) +- **Status**: ✅ Done +- **CAs cobertos**: CA01, CA04, CA12, CA13, CA14 +- **Persona**: [leitor autenticado](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Comentário em artigo publicado + Como leitor autenticado e não banido + Quero comentar abaixo do artigo + Para participar da conversa pública editorial + +Cenário: Leitor autenticado comenta com sucesso (caminho feliz) + Dado que estou autenticado como leitor não banido + E estou na página de um artigo publicado + Quando escrevo "Discordo da leitura sobre a Beyoncé na seção 3" no campo de comentário + E confirmo o envio + Então vejo o comentário aparecer no topo da lista de comentários + E o autor exibido é meu nome público + E a data exibida é "agora" + E o backend retornou 201 com o JSON do comentário criado + +Cenário: Leitor anônimo é bloqueado ao tentar comentar + Dado que NÃO estou autenticado + E estou na página de um artigo publicado + Quando tento enviar um comentário + Então o backend retorna 401 + E vejo um convite para fazer login ou criar conta + +Cenário: Leitor banido é bloqueado por defesa em profundidade + Dado que estou autenticado mas meu usuário está banido + Quando tento enviar um comentário + Então o backend retorna 403 (IsNotBanned negou) + E vejo a mensagem "Sua conta está bloqueada para esta ação" + +Cenário: Comentário em artigo draft é invisível e bloqueado + Dado que existe um artigo com status "draft" + Quando tento ler ou comentar nesse artigo + Então o backend retorna 404 (artigo não existe publicamente) + E nenhum comentário daquele artigo é exposto na API + +Cenário: Conteúdo acima de 2000 caracteres é rejeitado + Dado que estou autenticado como leitor não banido + Quando tento enviar comentário com 2001 caracteres + Então o backend retorna 400 ("Conteúdo excede limite de 2000 caracteres") + E meu comentário não foi salvo +``` + +--- + +### US20.2 — Leitor responde a comentário existente + +> **Como** leitor autenticado e não banido +> **Quero** responder a um comentário existente +> **Para** continuar uma conversa específica dentro da discussão do artigo. + +- **Prioridade**: 🟠 Alta +- **Estimativa**: 3 Story Points +- **Sprint**: 2-3 (retroativo) +- **Status**: ✅ Done +- **CAs cobertos**: CA02, CA03, CA08 +- **Persona**: leitor autenticado + +#### Cenários BDD + +```gherkin +Funcionalidade: Resposta a comentário existente (1 nível) + Como leitor autenticado + Quero responder a um comentário específico + Para continuar uma conversa pontual + +Cenário: Resposta válida ao comentário top-level do mesmo artigo + Dado que estou autenticado como leitor não banido + E existe um comentário top-level no artigo "artigo-A" + Quando envio POST com content="boa colocação" e parent_id=ID do comentário + Então o backend retorna 201 + E o comentário criado tem parent_id preenchido + E o comentário aparece como reply do parent, não como top-level + +Cenário: Resposta a comentário de OUTRO artigo é rejeitada + Dado que existe comentário-X no artigo "artigo-A" + E estou comentando no artigo "artigo-B" + Quando envio POST com parent_id=ID do comentário-X + Então o backend retorna 400 ("Comentário pai inválido ou não encontrado") + E nada é persistido + +Cenário: Resposta a um reply (nível 2) é rejeitada — aninhamento máximo é 1 + Dado que existe comentário-A top-level + E existe reply-B respondendo a comentário-A + Quando tento enviar reply-C com parent_id=ID do reply-B + Então o backend retorna 400 ("Comentário pai inválido ou não encontrado") + E nenhuma árvore com profundidade 2 é criada + +Cenário: Resposta a comentário soft-deletado é rejeitada + Dado que existe comentário-X com is_deleted=true + Quando tento enviar reply com parent_id=ID do comentário-X + Então o backend retorna 400 ("Comentário pai inválido ou não encontrado") + E meu reply não é persistido + +Cenário: Reply existente cujo parent é soft-deletado fica órfão (comportamento atual) + Dado que existe reply-B respondendo a comentário-A + E o comentário-A foi soft-deletado depois + Quando o leitor abre a thread do artigo + Então comentário-A NÃO aparece (filter is_deleted=False) + E reply-B NÃO aparece como top-level (parent!=None no filtro) + E reply-B continua no DB + # Débito OPS-1: UX final pendente — esconder tudo ou mostrar tombstone do parent? +``` + +--- + +### US20.3 — Leitor curte e descurte comentário + +> **Como** leitor autenticado e não banido +> **Quero** curtir/descurtir um comentário com um clique +> **Para** sinalizar concordância sem precisar escrever. + +- **Prioridade**: 🟠 Alta +- **Estimativa**: 3 Story Points +- **Sprint**: 2-3 (retroativo) +- **Status**: ✅ Done +- **CAs cobertos**: CA09, CA10, CA11 +- **Persona**: leitor autenticado + +#### Cenários BDD + +```gherkin +Funcionalidade: Toggle de curtida em comentário + Como leitor autenticado + Quero curtir ou descurtir um comentário + Para sinalizar concordância de forma simples + +Cenário: Primeira curtida (caminho feliz) + Dado que estou autenticado como leitor não banido + E existe comentário-X visível + E NÃO há CommentLike(comment=X, user=eu) + Quando envio POST /api/v1/comments/X/like/ + Então o backend retorna 200 com {liked: true, likes_count: N+1} + E foi criada uma CommentLike(comment=X, user=eu) + +Cenário: Segunda curtida descurte (toggle idempotente) + Dado que existe CommentLike(comment=X, user=eu) + Quando envio POST /api/v1/comments/X/like/ novamente + Então o backend retorna 200 com {liked: false, likes_count: N-1} + E a CommentLike anterior foi removida + +Cenário: Duplo-clique não cria duas curtidas (race condition) + Dado que estou autenticado + Quando disparo dois POSTs paralelos para /api/v1/comments/X/like/ + Então exatamente uma CommentLike(comment=X, user=eu) existe ao final + E o backend não retorna 500 (unique_together protege via get_or_create) + +Cenário: Leitor banido é bloqueado ao tentar curtir + Dado que estou autenticado mas banido + Quando envio POST /api/v1/comments/X/like/ + Então o backend retorna 403 (IsNotBanned negou) + E nenhuma CommentLike é criada + +Cenário: Like em comentário soft-deletado retorna 404 + Dado que comentário-X tem is_deleted=true + Quando envio POST /api/v1/comments/X/like/ + Então o backend retorna 404 + E nenhuma CommentLike é criada +``` + +--- + +## Tasks (implementação) + +### Tasks US-bound (T20.X.Y — todas ✅ Done Sprint 2-3, pre-busca) + +| ID | Descrição | Prioridade | Status | +| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------- | +| T20.1.1 | Bootstrap Django app `apps.comments` (`AppConfig`, registro em `INSTALLED_APPS`) | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.2 | Model `Comment` com UUID PK, FKs `article`/`author`/`parent` self-FK, `content` 2000 chars | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.3 | Campos de soft-delete em `Comment`: `is_deleted` (default False, `db_index=True`), `deleted_at`, `deleted_by` FK `SET_NULL` | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.4 | Meta indexes em `Comment`: `(article, parent, -created_at)` e `(author, -created_at)` | 🟡 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.5 | `CommentSerializer` (read + write) com annotations `likes_count`, `is_liked`, `replies_count`, nested `replies` (1 nível) | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.6 | `ReplySerializer` (leaf — sem `replies`/`replies_count`) — corta árvore em profundidade 1 | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.7 | `validate_parent_id` na `CommentSerializer` — exige mesmo artigo + `is_deleted=False` + `parent=None` (invariante I8) | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.8 | `CommentListCreateView` (GET `AllowAny`, POST `IsAuthenticated+IsNotBanned`) + lookup por `` filtrado `status='published'` | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.1.9 | `CommentDestroyView` com `IsAuthenticated+IsOwnerOrAdmin` + `perform_destroy` sobrescrito (soft-delete via `update_fields`) | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.2.1 | Model `CommentLike` com UUID PK, FKs `comment`/`user`, `unique_together('comment','user')` para idempotência | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.2.2 | `CommentLikeToggleView` com `get_or_create` + `like.delete()` para toggle; resposta `{liked, likes_count}` | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.2.3 | Annotation `is_liked` via `Exists(CommentLike.objects.filter(user=request.user, comment=OuterRef))` | 🟡 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.3.1 | Permissão `IsNotBanned` aplicada em POST de comentário, DELETE e POST de like (defesa em profundidade S8) | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.3.2 | Permissão `IsOwnerOrAdmin` (object-level) — owner OU admin removem; editor não remove de terceiros | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.4.1 | URLs em `apps.comments.urls`: GET/POST `/api/v1/articles//comments/`, DELETE `/api/v1/comments//`, POST `/api/v1/comments//like/` | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.4.2 | Conversor de path `` registrado em `ArticlesConfig.ready()` — desacopla `comments` de `articles` no roteamento | 🟡 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.5.1 | Admin Django `apps.comments.admin` — busca por email do autor + content, filtro `is_deleted`, `deleted_at` read-only | 🟡 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.6.1 | Tests `tests/test_views.py` — 24 cenários de integração (DB real via `pytest-django`) | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | +| T20.6.2 | Migration `0003_commentlike_and_more` — adiciona `parent` self-FK, índice composto, `CommentLike` com unique + (índice redundante D-04) | 🟡 | ✅ Done (Sprint 2-3, pre-busca) | + +### Tasks transversais (TX-NN — débitos de housekeeping pendentes) + +| ID | Descrição | Prioridade | Status | +| ------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------------------------------------- | +| TX-C01 | **HOTFIX** `ScopedRateThrottle` específico `comments_create` (S-07) — 15 LoC, pattern já vivo em `apps.users:32` | 🔴 | ⏳ Sprint de moderação (candidate hotfix) | +| TX-C02 | Sanitização HTML server-side de `content` via `bleach` ou `nh3` (S-01) — fecha XSS persistente em regressão de FE | 🟠 | ⏳ Sprint de moderação | +| TX-C03 | Migration `RemoveIndex` para corrigir D-04 (índice redundante em `CommentLike`) | 🟡 | ⏳ Sprint de housekeeping | +| TX-C04 | Decisão de UX + implementação para replies órfãs em parent soft-deletado (OPS-1) | 🟡 | ⏳ Decisão de produto | +| TX-C05 | ADR + cron de retenção para comentários soft-deletados (OPS-2; LGPD) | 🟡 | ⏳ Sprint de moderação | +| TX-C06 | Teste direto para invariante I8 (nesting ≤ 1) — fecha GAP-1 | 🟢 | ⏳ Sprint de housekeeping | +| TX-C07 | Decisão sobre `updated_at`: remover campo OU criar endpoint PATCH com janela de tempo (GAP-2) | 🟢 | ⏳ Decisão pendente | +| TX-C08 | Suite a11y automatizada para ``, ``, `` (axe-core no FE) | 🟡 | ⏳ Sprint de housekeeping | +| TX-C09 | Formalizar 5 decisões implícitas em ADRs em `docs/specs/comments/adrs/` (soft-delete, 1 nível, sem signals, IsNotBanned, unique_together) | 🟡 | ⏳ Sprint de housekeeping | + +--- + +## Open Questions (decisões pendentes — pré-Sprint de moderação) + +1. **OPS-1 — Replies órfãs em parent soft-deletado**: hoje parent some da API (`filter(is_deleted=False, parent=None)`), e reply também some (não aparece como top-level porque `parent!=None`). Reply continua no DB. Decisão pendente: **(a)** esconder a thread inteira (estado atual de fato), **(b)** exibir tombstone do parent (`"comentário removido"`) com os replies aninhados abaixo, ou **(c)** promover replies a top-level quando parent é removido. Sem documentação de UX vigente — pergunta de produto. + +2. **D-04 — Índice redundante em `CommentLike`**: `unique_together('comment','user')` + `Index(fields=['comment','user'])` na mesma combinação. Postgres já cria índice automático pela unique constraint. Migration `RemoveIndex` resolve sem alterar comportamento — agendar em Sprint de housekeeping (referência: `migrations/0003_commentlike_and_more.py:51-58`). + +3. **S-07 — Sem `ScopedRateThrottle` para `comments_create`**: pattern já existe e roda em `apps/users/views.py:32`. Default `user=1000/h` permite flood massivo em artigo viral (1 leitor pode esgotar capacidade de moderação reativa). **Hotfix óbvio** — provavelmente 15 LoC. Risco aumenta com qualquer evento de viralização orgânica. **Prioridade real: 🔴 Imediato** — deveria sair na próxima janela aberta, não esperar Sprint formal. + +4. **F-21 — Notificação por resposta**: leitor que recebeu reply deveria ser notificado? Canal (e-mail vs in-app), opt-out, agregação (1 e-mail/hora vs 1 por reply). Decisão de produto bloqueia design técnico. + +5. **Threading multi-nível** (relaxar invariante I8): hoje invariante hard-coded em `validate_parent_id`. Avaliar **somente** se feedback de leitor pedir conversas mais aninhadas — trade-off de legibilidade vs profundidade. Default: manter 1 nível. + +6. **GAP-2 — `updated_at` órfão**: campo no schema atualiza em qualquer `save()`, mas não há endpoint de edição. Ou se cria `PATCH /api/v1/comments//` com janela curta (ex.: 5min após criação), ou se remove o campo (clareza arquitetural). Sem decisão. + +--- + +## Definition of Done — verificação (retroativa) + +- [x] CA01, CA02, CA04, CA05, CA06, CA07, CA09, CA10, CA11, CA14, CA15 verificados por test automatizado (24 testes em `apps/comments/tests/test_views.py`) +- [ ] **CA12 NÃO atendido** — débito S-07 (throttle `comments_create` ausente) +- [x] CA03 atendido por código (`validate_parent_id`) mas sem teste direto da invariante (GAP-1) +- [ ] CA08 sem decisão de UX (OPS-1) +- [ ] CA13 verificado manualmente; sem CI gate a11y para `apps.comments` +- [x] US20.1, US20.2, US20.3 com cenários BDD descrevendo o comportamento implementado +- [x] Todas as Tasks US-bound (T20.X.Y) ✅ Done em código +- [ ] Tasks transversais (TX-C01..TX-C09) ⏳ pendentes (Sprint de moderação) +- [x] Mergeada em main pré-Sprint 4 (sem PR de referência registrado — pré-formalização do processo) +- [x] Documentação retroativa criada em 2026-06-09: [RF-002](../../requirements/RF/RF-002-comments.md), [EP-03](../epics/EP-03-engajamento-comunidade.md), [DESIGN.md](../../specs/comments/DESIGN.md), F-20 (este arquivo) + +**Status final**: ✅ **Done em código (Sprint 2-3, pre-busca)**, com **1 CA aberto (CA12) de prioridade real 🔴 Imediato** e 6 débitos rastreados (S-01, S-07, D-04, OPS-1, OPS-2, GAP-1, GAP-2) prontos para Sprint dedicada de moderação. + +--- + +## Specs técnicas relacionadas + +- [DESIGN.md](../../specs/comments/DESIGN.md) — modelo de dados, contrato público, fluxos críticos (4 sequence diagrams), 8 invariantes, débitos com referências a `models.py:LL` e `views.py:LL` +- _ADRs de `comments` (pendentes formalização — débito TX-C09)_ + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-002](../../requirements/RF/RF-002-comments.md), [RNF-security](../../requirements/RNF/RNF-security.md), [RNF-a11y](../../requirements/RNF/RNF-a11y.md), [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md), [RNF-availability](../../requirements/RNF/RNF-availability.md) | +| ↑ Epic pai | [EP-03 Engajamento da comunidade](../epics/EP-03-engajamento-comunidade.md) | +| → Sprint(s) | Sprint 2-3 (retroativo, sem arquivo); Sprint de moderação (futuro) para TX-C01..TX-C09 | +| → Specs técnicas | [DESIGN.md](../../specs/comments/DESIGN.md) | +| → Features irmãs sob EP-03 | F-21 Notificações por resposta (backlog), F-22 Anti-spam reativo (backlog) | +| ← Improvement-system | [§11.6 S8 — IsNotBanned defense-in-depth](../../planning/Improvement-system.md) | + +--- + +_F-20 ✅ Done em código desde Sprint 2-3 (pré-formalização). Documentação retroativa criada em 2026-06-09 como parte de `chore/docs-reorg`. Próxima ação operacional: priorizar TX-C01 (hotfix S-07 throttle) na próxima janela aberta — risco de flood em viralização orgânica não justifica esperar Sprint formal._ diff --git a/docs/backlog/features/F-30-busca-texto-livre.md b/docs/backlog/features/F-30-busca-texto-livre.md new file mode 100644 index 00000000..cf36e9f6 --- /dev/null +++ b/docs/backlog/features/F-30-busca-texto-livre.md @@ -0,0 +1,246 @@ +# F-30 — Busca por texto livre + +> **Tipo**: Feature +> **Epic pai**: [EP-10 Busca editorial](../epics/EP-10-busca-editorial.md) +> **Sprint de execução**: [Sprint 4](../sprints/sprint-4-busca-editorial.md) +> **Status**: ✅ Done — PR #37 squash-merged em main como `2bdf73b` em 2026-06-09 +> **Prioridade**: 🔴 Imediato (MVP da descoberta editorial) + +--- + +## Descrição (visão de produto) + +Leitor entra em `/buscar`, digita um termo (mínimo 2 caracteres) e vê uma lista de artigos publicados rankeados por relevância editorial e recência. Os termos buscados aparecem destacados nos títulos e resumos. Quando não há resultado, vê uma mensagem clara. Quando faz muitas buscas em pouco tempo, vê uma mensagem amigável pedindo para aguardar. A página inteira funciona sem JavaScript habilitado (fallback) e é acessível por teclado e leitor de tela. + +Esta Feature é a **fundação** da descoberta editorial — Features futuras (F-31 filtros, F-32 deep-linking) constroem em cima dela. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| --------------------------------------------------------- | ------------------------------------------------- | ------------------- | +| [RF-007](../../requirements/RF/RF-007-busca-editorial.md) | Busca por texto livre nos artigos publicados | Realiza diretamente | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | p95 ≤ 300ms server · LCP/INP/CLS dentro dos gates | Realiza CA02 | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Throttle, HMAC cursor, XSS escape | Realiza CA10/CA13 | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | WCAG 2.2 AA em todos os estados | Realiza CA08 | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | search_log 7d com pseudonimização | Realiza CA14 | + +--- + +## Critérios de Aceitação (CAs) + +| ID | Critério | Como verificar | Status | +| -------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------ | ---------------------------------- | +| **CA01** | Termo válido tem 2-200 caracteres; abaixo de 2 não dispara request HTTP | Test integração `useSearch enabled rule` | ✅ | +| **CA02** | Resultados aparecem em ≤ 300ms p95 server em 50k artigos com cache miss | k6 load test Zipfiano (T30.1.X22 Sprint 5) | 🟡 medido manualmente até Sprint 5 | +| **CA03** | Termos buscados aparecem destacados em título e resumo dos cards | Test `HighlightedText` + integração | ✅ | +| **CA04** | Sem resultados mostra "Nada encontrado para 'X'" com sugestão | Test `EmptyResults` | ✅ | +| **CA05** | Erro de rede mostra "Não foi possível buscar agora" + botão "Tentar novamente" que reativa busca | Test `SearchErrorFallback` + ErrorBoundary | ✅ | +| **CA06** | Rate limit (429) mostra "Muitas buscas em pouco tempo. Aguarde Xs" com countdown | Test `RateLimitedState` | ✅ | +| **CA07** | Input fica responsivo enquanto resultados carregam (deferred render) | Test useDeferredValue + manual smoke | ✅ | +| **CA08** | Página passa axe-core nos 5 estados em light + dark + mobile + desktop | Test `a11y.test.tsx` (12 cenários) | ✅ | +| **CA09** | URL contém o termo (`/buscar?q=X`) — back button retorna estado anterior | Test `useSearchParamsState` | ✅ | +| **CA10** | Termos XSS (`" + Então o navegador não executa o script + E o termo aparece escapado no campo (sem virar HTML) +``` + +--- + +## Tasks (implementação) + +### Tasks US-bound (T30.1.X — todas ✅ Done) + +| ID | Descrição | Prioridade | Commit | Sprint | +| --------- | ---------------------------------------------------------------------------------- | ---------- | --------------------- | ------ | +| T30.1.1 | Criar Django app `apps.search` + estrutura de pastas | 🔴 | `c017e1f` | 4 | +| T30.1.2 | Models `SearchIndex`/`SearchLog` com `managed=False` | 🔴 | `c017e1f` | 4 | +| T30.1.3 | Migration 0002 — GIN + composite parciais + covering | 🟠 | `d43e17d` | 4 | +| T30.1.4b | Migration 0001 — extension unaccent + CONFIGURATION pt_unaccent + função IMMUTABLE | 🔴 | `103e5ea` | 4 | +| T30.1.5b | Migration 0003 — trigger SQL `articles_sync_search` (SSOT) | 🔴 | `df98846` | 4 | +| T30.1.5c | Signal Python — apenas cache invalidation (sem upsert) | 🟠 | `36b21e2` | 4 | +| T30.1.5d | Migration 0005 — `ENABLE ALWAYS` triggers (fix bypass `session_replication_role`) | 🔴 | `ffb88f6` | 4 | +| T30.1.7 | `SearchService.query()` com 12 invariantes algorithms | 🟠 | `f5b226c` | 4 | +| T30.1.8 | `SearchArticlesView` + `SearchQuerySerializer` + URL | 🟠 | `e4ce5df` | 4 | +| T30.1.X1 | Migration 0004 — vacuum tuning GIN + autovacuum | 🟠 | `64c49d9` | 4 | +| T30.1.X2 | `utils.normalize_search_text()` simétrico (signal + service) | 🟠 | `3c98825` | 4 | +| T30.1.X3 | `estimate_total()` com floor por `len(results)` | 🟡 | `f5b226c` | 4 | +| T30.1.X4 | Feature flag `SEARCH_FEATURE_ENABLED` → 503 + Retry-After | 🟠 | `e4ce5df` | 4 | +| T30.1.X5 | `query_terms_expanded` via `ts_lexize('portuguese_stem')` | 🟠 | `f5b226c` | 4 | +| T30.1.X6 | Hook `useDebouncedValue(value, delayMs)` 15 LoC zero-dep | 🔴 | `ce18826` | 4 | +| T30.1.X7 | Fix Bug 6 — `getNextPageParam: ?? undefined` | 🔴 | `2259605` | 4 | +| T30.1.X8 | `
` + `` (rejeita combobox APG) | 🔴 | `816e3fb` | 4 | +| T30.1.X9 | Resilient sub-tree `ErrorBoundary` em `` | 🟠 | `c1caa0c` + `db4b2a2` | 4 | +| T30.1.X10 | `` com `mark.js` via refs (CSP-safe) | 🟡 | `871f53a` | 4 | +| T30.1.X11 | Tokens herdados — sem fork da paleta editorial | 🟠 | `74a9dc9` | 4 | +| T30.1.X12 | MSW handlers + worker DEV-only + READMEs | 🔴 | `ffa5150` + `2bdf681` | 4 | +| T30.1.X13 | `a11y.test.tsx` com vitest-axe — 12 cenários nos 5 estados + fix Skeleton landmark | 🔴 | `cbb9001` | 4 | +| T30.1.X14 | DRY — `SEARCH_STALE_TIME`/`SEARCH_GC_TIME` em searchService.ts | 🟠 | `25bb5f9` | 4 | +| T30.1.X15 | FilterChips — validação `category` Number.isFinite + Integer | 🟠 | `25bb5f9` | 4 | +| T30.1.X16 | HighlightedText — cleanup `return () => unmark()` no useEffect | 🟠 | `25bb5f9` | 4 | +| T30.1.X17 | ResultCard — `data-variant` + tokens editoriais `--clr-cat-*` | 🟠 | `d45478f` | 4 | +| T30.1.13 | Rota lazy `/buscar` + `` page | 🟠 | `db4b2a2` | 4 | +| T30.1.15 | `` component | 🟠 | `816e3fb` | 4 | +| T30.1.16 | Hook `useSearch` + `useSearchParamsState` | 🟠 | `2259605` | 4 | +| T30.1.17 | `` thumb-left 120×80 anti-CLS | 🟠 | `871f53a` | 4 | +| T30.3.1-4 | 5 estados (Empty/Loading/Results/NoResults/Error/RateLimited) | 🟠 | `db4b2a2` + `c1caa0c` | 4 | +| T30.4.1-4 | DRF throttles anon/user/global + cache Redis backend | 🟠 | `dc4680c` | 4 | +| T30.4.X4 | Cache key SHA256(canonical+auth_tier) | 🟠 | `0bd7e33` | 4 | +| T30.4.B1 | F2-B-01 — `@transaction.atomic` em `_query_postgres` (Inv #12 runtime) | 🟠 | `14649d7` | 4 | +| T30.4.B2 | F2-B-02 — `Cache-Control: private` para autenticado | 🟠 | `2362305` | 4 | +| T30.4.B3 | F2-B-03 — `SEARCH_CURSOR_HMAC_SECRET` hard-fail em prod | 🟠 | `96cdad5` | 4 | + +### Tasks transversais (TX-NN) + +| ID | Descrição | Prioridade | Commit/Status | Sprint | +| ----- | ---------------------------------------------------------------------------------------------- | ---------- | --------------------------------------------- | ------ | +| TX-13 | Runbook DR — `pg_dump --exclude-table-data` + reindex pós-restore | 🟡 | ⏳ Sprint 5 | 5 | +| TX-14 | Doc scaling triggers — `>100GB OR p95>250ms` | ⚪ | ⏳ Sprint 5 | 5 | +| TX-15 | Role Postgres `interpop_search_reader` (statement_timeout + work_mem + gin_fuzzy_search_limit) | 🟠 | ⏳ Sprint 5 (env-ops) | 5 | +| TX-16 | Lighthouse CI gate em `/buscar?q=kpop` bloqueia PR | 🟠 | ⏳ Sprint 5 | 5 | +| TX-17 | jest-axe + axe-playwright nos 5 estados E2E | 🟠 | 🟡 axe-vitest parte (T30.1.X13); E2E Sprint 5 | 5 | +| TX-18 | Baseline Lighthouse pré-busca | 🔴 | ✅ `284997a` (4 JSONs em docs/performance/) | 4 | + +### Tasks restantes Sprint 5 (do REVIEW-PHASE-3) + +| ID | Descrição | Prioridade | +| --------- | ---------------------------------------------------------------- | ---------- | +| T30.1.X18 | Tests para `useSearchParamsState` (NaN guard, replace vs push) | 🟡 | +| T30.1.X19 | Test AbortSignal cancelando `fetchSearch` | 🟡 | +| T30.1.X20 | Visual regression Playwright `toHaveScreenshot` 5 estados | 🟡 | +| T30.1.X21 | E2E Playwright (input → results → load-more → article) | 🟡 | +| T30.1.X22 | Property-based (fast-check) `useDebouncedValue` + `canonicalKey` | 🟡 | +| T30.1.X23 | Avaliar custom 30-LoC highlighter vs mark.js 8 KB gz | ⚪ | +| T30.1.X24 | i18n extract strings pt-BR para `src/i18n/` | ⚪ | + +--- + +## Definition of Done — verificação + +- [x] CA01–CA13, CA15 verificados por test automatizado +- [ ] CA02, CA11, CA14 verificáveis Sprint 5 (k6 + Lighthouse CI + cron retention) +- [x] US30.1 com cenários BDD rodando verde (78 tests `pages/Buscar/`) +- [x] Todas as Tasks 🔴 Imediate done com commit hash +- [x] Code-review aprovado (Phases 1/2/3 + fixes inline + F2-B-\* fixes) +- [x] Cobertura backend ≥ 85% local, frontend ≥ 80% (`pages/Buscar` 84.15%) +- [x] Documentação cruzada atualizada — RF-007 + RNF-\* citam esta Feature, EP-10 lista +- [x] Mergeada em main via PR #37 squash em `2bdf73b` (2026-06-09) + +**Status final**: ✅ **Done** com 3 CAs (CA02/CA11/CA14) marcados para verificação automatizada em Sprint 5. + +--- + +## Specs técnicas relacionadas + +- [DESIGN.md v3](../../specs/busca-editorial/DESIGN.md) — arquitetura completa por camada (1090 LOC) +- [REVIEW-PHASE-1.md](../../specs/busca-editorial/REVIEW-PHASE-1.md) — DB schema review +- [REVIEW-PHASE-2.md](../../specs/busca-editorial/REVIEW-PHASE-2.md) — Backend review (F2-B-01/02/03 originaram aqui) +- [REVIEW-PHASE-3.md](../../specs/busca-editorial/REVIEW-PHASE-3.md) — Frontend review (BLOQUEIOs 1/2 + H-01..H-04) +- [SECURITY-REVIEW.md](../../specs/busca-editorial/SECURITY-REVIEW.md) — 17 achados auditados +- [TEST-STRATEGY.md](../../specs/busca-editorial/TEST-STRATEGY.md) — matriz 10 tipos + 110 testes projetados +- [35 ADRs](../../specs/busca-editorial/adrs/INDEX.md) — decisões locked-in por camada +- [4 specialist outputs literais](../../specs/busca-editorial/_specialist-outputs/) — DB/Algo/FE/UI fan-out + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-007](../../requirements/RF/RF-007-busca-editorial.md), [RNF-perf](../../requirements/RNF/RNF-perf.md), [RNF-security](../../requirements/RNF/RNF-security.md), [RNF-a11y](../../requirements/RNF/RNF-a11y.md), [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | +| ↑ Epic pai | [EP-10](../epics/EP-10-busca-editorial.md) | +| → Sprint(s) | [Sprint 4](../sprints/sprint-4-busca-editorial.md) (entrega), [Sprint 5](../sprints/sprint-5-filtros-deep-linking.md) (refino + Tasks restantes) | +| → Specs técnicas | [DESIGN.md v3](../../specs/busca-editorial/DESIGN.md) + ADRs 015-045 | +| → Features filhas | n/a (F-30 é Feature, não Epic) | +| ← Features irmãs sob EP-10 | [F-31 Filtros](F-31-filtros-busca.md), [F-32 Deep-linking](F-32-deep-linking-busca.md) | + +--- + +_F-30 ✅ Done — squash-merged em main como `2bdf73b` (2026-06-09). Próxima ação: arquivar para `done/` quando Sprint 4 fechar formalmente._ diff --git a/docs/backlog/features/F-31-filtros-busca.md b/docs/backlog/features/F-31-filtros-busca.md new file mode 100644 index 00000000..e898882b --- /dev/null +++ b/docs/backlog/features/F-31-filtros-busca.md @@ -0,0 +1,122 @@ +# F-31 — Filtros (autor, editoria, intervalo de datas) + +> **Tipo**: Feature +> **Epic pai**: [EP-10 Busca editorial](../epics/EP-10-busca-editorial.md) +> **Sprint de execução**: [Sprint 5](../sprints/sprint-5-filtros-deep-linking.md) +> **Status**: ⏳ Pending (Sprint 5) +> **Prioridade**: 🟠 Alta + +--- + +## Descrição (visão de produto) + +Em cima da busca por texto livre (F-30), leitor pode refinar resultados por **autor**, **editoria** e **intervalo de datas de publicação**. Filtros são exibidos como chips removíveis abaixo do campo de busca. No mobile, abrem em uma folha (sheet) que respeita o teclado virtual. + +Esta Feature **estende** F-30 — não a substitui. A shell `` já foi entregue vazia no Sprint 4; aqui plugamos popovers funcionais. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------- | +| [RF-007](../../requirements/RF/RF-007-busca-editorial.md) §filtros | Busca aceita filtros opcionais que reduzem o conjunto resultante | Realiza diretamente | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | p95 ≤ 300ms mesmo com filtros (composite indexes parciais já presentes — ADR-030-DB) | Mantém | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Popover/sheet acessível por teclado; chips com `aria-pressed` | Estende | + +--- + +## Critérios de Aceitação (CAs — escopo Sprint 5) + +| ID | Critério | Como verificar | Status | +| -------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------ | +| **CA16** | Autor selecionável por popover com search-as-you-type (lista paginada) | Test integração FilterAuthor | ⏳ | +| **CA17** | Editoria selecionável por dropdown com as 5 categorias canônicas + ícones | Test FilterCategory | ⏳ | +| **CA18** | Intervalo de datas selecionável por dois date-pickers (de/até) com validação | Test FilterDateRange + FormValidation | ⏳ | +| **CA19** | Chip de cada filtro ativo aparece com botão "remover" (`×`) | Test FilterChips (extensão de chip vazio do Sprint 4) | ⏳ | +| **CA20** | Mobile (≤ 640px) abre filtros em `` HTML respeitando `dvh` + safe-area + keyboard | Test responsive + manual iOS/Android | ⏳ | +| **CA21** | Limpar todos os filtros com 1 clique mantém o termo `q` | Test FilterChips clear-all | ⏳ | + +--- + +## User Stories (rascunho — refinar no kickoff do Sprint 5) + +### US31.1 — Leitor filtra busca por autor + +> **Como** leitor +> **Quero** ver apenas artigos de uma autora específica +> **Para** ler trabalhos da curadora que sigo. + +- **Prioridade**: 🟠 Alta +- **CAs cobertos**: CA16, CA19, CA21 +- **Status**: ⏳ Pending Sprint 5 + +### US31.2 — Leitor filtra busca por editoria + +> **Como** leitor +> **Quero** restringir a busca à editoria "Música" (ou outra) +> **Para** explorar apenas conteúdo daquela seção. + +- **Prioridade**: 🟠 Alta +- **CAs cobertos**: CA17, CA19, CA21 +- **Status**: ⏳ Pending Sprint 5 + +### US31.3 — Leitor filtra busca por intervalo de datas + +> **Como** leitor +> **Quero** ver artigos publicados entre duas datas +> **Para** revisitar análise editorial de um período específico. + +- **Prioridade**: 🟡 Normal +- **CAs cobertos**: CA18, CA19, CA21 +- **Status**: ⏳ Pending Sprint 5 + +### US31.4 — Leitor usa filtros em mobile sem fricção + +> **Como** leitor em smartphone +> **Quero** abrir os filtros sem que o teclado quebre o layout +> **Para** filtrar buscas em qualquer dispositivo. + +- **Prioridade**: 🟠 Alta +- **CAs cobertos**: CA20 +- **Status**: ⏳ Pending Sprint 5 + +> **Cenários BDD detalhados serão escritos no kickoff do Sprint 5**, alinhados ao mockup final do popover/sheet. + +--- + +## Tasks previstas (alocação Sprint 5 — refinar antes do start) + +| ID | Descrição | Prioridade | +| ----- | --------------------------------------------------------------------------------------------------------------------- | ---------- | +| T31.1 | Backend — endpoint aceita combinação `q + author + category + de/ate` (já parcialmente implementado em SearchService) | 🟠 | +| T31.2 | `` popover com lookup + search-as-you-type (axios search autores) | 🟠 | +| T31.3 | `` dropdown com 5 categorias hardcoded (depois service) | 🟠 | +| T31.4 | `` com date-pickers nativos `` + validação | 🟡 | +| T31.5 | Mobile `` HTML sheet com dvh + safe-area + close-on-input-focus | 🟠 | +| T31.6 | Tests integração combinando filtros (Hypothesis combinatorial) | 🟡 | +| T31.7 | A11y E2E axe-playwright com filtros ativos | 🟠 | + +--- + +## Specs técnicas relacionadas + +- Backend já preparado: `SearchQuerySerializer` aceita filtros, `SearchService.query()` aplica WHERE condicional (ver `apps/search/services.py:240-247`) +- Composite indexes parciais `(author_id, published_at DESC)` + `(category_id, published_at DESC)` já criados na migration 0002 ([ADR-030-DB](../../specs/busca-editorial/adrs/ADR-030-DB-composite-indexes-parciais-covering.md)) +- Tokens UI: `--clr-chip-bg`, `--clr-chip-on` (ADR-030-UI), `radius-md`, mobile `` patterns (ui-ux-architect specialist output) + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-007 §filtros](../../requirements/RF/RF-007-busca-editorial.md), [RNF-perf](../../requirements/RNF/RNF-perf.md), [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | +| ↑ Epic pai | [EP-10](../epics/EP-10-busca-editorial.md) | +| → Sprint(s) | [Sprint 5](../sprints/sprint-5-filtros-deep-linking.md) | +| → Specs técnicas | [DESIGN.md v3 §2.6 mobile dialog](../../specs/busca-editorial/DESIGN.md) + ADRs 030-UI, 030-DB | +| ← Features irmãs sob EP-10 | [F-30 Texto livre](F-30-busca-texto-livre.md) (dependência hard), [F-32 Deep-linking](F-32-deep-linking-busca.md) (dependência soft — deep-linking precisa de filtros para fazer sentido) | + +--- + +_Pending — kickoff previsto Sprint 5._ diff --git a/docs/backlog/features/F-32-deep-linking-busca.md b/docs/backlog/features/F-32-deep-linking-busca.md new file mode 100644 index 00000000..30bf4330 --- /dev/null +++ b/docs/backlog/features/F-32-deep-linking-busca.md @@ -0,0 +1,99 @@ +# F-32 — Deep-linking + compartilhamento da busca + +> **Tipo**: Feature +> **Epic pai**: [EP-10 Busca editorial](../epics/EP-10-busca-editorial.md) +> **Sprint de execução**: [Sprint 5](../sprints/sprint-5-filtros-deep-linking.md) +> **Status**: ⏳ Pending (Sprint 5) +> **Prioridade**: 🟡 Normal + +--- + +## Descrição (visão de produto) + +Leitor pode copiar a URL da busca atual e enviar para outra pessoa — ela abre exatamente o mesmo estado (termo + filtros + página de paginação). Botão "Compartilhar" oferece atalho Web Share API em mobile, fallback cópia para clipboard em desktop. URL canonicalizada (ordem de params estável) para evitar fragmentação de cache. + +URL como SSOT (single source of truth) já está implementada em F-30 para `?q=`. Esta Feature **completa** isso para filtros + paginação + adiciona compartilhamento explícito. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| -------------------------------------------------------------------- | --------------------------------------------------------------- | ------------------- | +| [RF-007](../../requirements/RF/RF-007-busca-editorial.md) §deep-link | Estado da busca é serializado em URL navegável e compartilhável | Realiza diretamente | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Botão compartilhar acessível por teclado + label clara | Estende | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Cursor de paginação na URL é HMAC-assinado, não exposto plain | Mantém | + +--- + +## Critérios de Aceitação (CAs — escopo Sprint 5) + +| ID | Critério | Como verificar | Status | +| -------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ------ | +| **CA22** | URL contém todos os filtros ativos em ordem canônica (`?q=X&author=Y&category=Z&de=A&ate=B&cursor=...`) | Test `useSearchParamsState canonical order` | ⏳ | +| **CA23** | Copiar/colar URL em outra aba abre busca no mesmo estado | Test E2E Playwright multi-tab | ⏳ | +| **CA24** | Botão "Compartilhar" usa Web Share API em mobile; cai em clipboard em desktop | Test feature-detection + manual smoke | ⏳ | +| **CA25** | Mudar `q` reseta `cursor` (corrige inconsistência de paginação) | Test integração `useSearchParamsState.setQ` | ⏳ | +| **CA26** | URL ≤ 2KB mesmo com filtros máximos (8 tokens + range + cursor) | Property-based test | ⏳ | +| **CA27** | Refresh (F5) mantém estado da busca | Test integração + manual smoke | ⏳ | + +--- + +## User Stories (rascunho) + +### US32.1 — Leitor compartilha resultado de busca + +> **Como** leitor +> **Quero** copiar a URL atual da busca +> **Para** mandar para um amigo discutir o resultado. + +- **Prioridade**: 🟡 Normal +- **CAs cobertos**: CA22, CA23, CA24 +- **Status**: ⏳ Pending Sprint 5 + +### US32.2 — Leitor abre URL compartilhada e vê o mesmo estado + +> **Como** leitor que recebeu uma URL de busca +> **Quero** ver exatamente os mesmos resultados que a pessoa que me enviou +> **Para** participar da conversa. + +- **Prioridade**: 🟡 Normal +- **CAs cobertos**: CA22, CA23, CA27 +- **Status**: ⏳ Pending Sprint 5 + +### US32.3 — Sistema garante paginação consistente em URL + +> **Como** sistema +> **Quero** que mudanças em `q` resetem o cursor de paginação +> **Para** que páginas profundas de termo antigo não vazem em busca nova. + +- **Prioridade**: 🟠 Alta (correção de inconsistência latente M-01 do REVIEW-PHASE-3) +- **CAs cobertos**: CA25 +- **Status**: ⏳ Pending Sprint 5 + +--- + +## Tasks previstas (alocação Sprint 5) + +| ID | Descrição | Prioridade | +| ----- | ---------------------------------------------------------------------- | ---------- | +| T32.1 | Botão `` com `navigator.share()` + fallback clipboard | 🟡 | +| T32.2 | `useSearchParamsState.setQ` reseta `cursor` (fix M-01) | 🟠 | +| T32.3 | Garantir ordem canônica dos params (refac `canonicalKey` espelhando) | 🟡 | +| T32.4 | Test E2E Playwright multi-tab (copy URL → open new tab → assert state) | 🟡 | + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-007 §deep-link](../../requirements/RF/RF-007-busca-editorial.md), [RNF-a11y](../../requirements/RNF/RNF-a11y.md), [RNF-security](../../requirements/RNF/RNF-security.md) | +| ↑ Epic pai | [EP-10](../epics/EP-10-busca-editorial.md) | +| → Sprint(s) | [Sprint 5](../sprints/sprint-5-filtros-deep-linking.md) | +| → Specs técnicas | [ADR-027 — URL SSOT](../../specs/busca-editorial/adrs/ADR-027-tanstack-query-usedebounced-deferred-url-ssot.md) | +| ← Features irmãs sob EP-10 | [F-30 Texto livre](F-30-busca-texto-livre.md) (dependência hard — F-32 estende SSOT já parcialmente implementado), [F-31 Filtros](F-31-filtros-busca.md) (deep-link só faz sentido com filtros funcionais) | + +--- + +_Pending — depende de F-31 estar funcional. Kickoff Sprint 5._ diff --git a/docs/backlog/features/F-40-newsletter-editorial.md b/docs/backlog/features/F-40-newsletter-editorial.md new file mode 100644 index 00000000..fd09b340 --- /dev/null +++ b/docs/backlog/features/F-40-newsletter-editorial.md @@ -0,0 +1,343 @@ +# F-40 — Newsletter editorial + +> **Tipo**: Feature +> **Epic pai**: [EP-04 Newsletter e comunicação](../epics/EP-04-newsletter-comunicacao.md) +> **Sprint de execução**: Sprint 2-3 (pre-busca) — implementação original · documentação retroativa Sprint 5 (chore/docs-reorg, PR #39) +> **Status**: ✅ Done — em produção desde Sprint 3 · 🔴 **2 hotfix candidatos invisíveis** (BUG-1/BUG-2) +> **Prioridade**: 🟠 Alta (canal owned crítico de retenção) + +--- + +## Descrição (visão de produto) + +Leitor (anônimo ou autenticado) digita seu email num formulário público (footer do site, modal de fim de artigo) e clica em "Inscrever". Sistema confirma na hora ("Inscrição realizada com sucesso!") e dispara em background um **email de boas-vindas** com link único de descadastro. + +A partir desse momento, **toda vez que editor/admin publica um novo artigo**, o leitor recebe automaticamente um email com título, resumo, imagem de capa e link para o artigo completo. Quando quiser cancelar, basta clicar no link de descadastro presente em qualquer email — uma única ação resolve, sem precisar logar. + +Esta Feature é a **fundação completa do canal newsletter** — F-41 (bounce + open rate), F-42 (segmentação) e F-43 (A/B subject) constroem em cima dela. + +### Anti-sycophancy — o que esta Feature NÃO entrega + +> Esta documentação é retroativa. **Sou direto sobre limites reais**: + +- **Não há open rate tracking** — sem pixel, sem UTM padronizado. Métrica de engajamento zero hoje. Backlog F-41. +- **Não há bounce handling** — hard-bounce reentra em toda publicação e degrada reputação SMTP. Backlog F-41. +- **Não há segmentação** — leitor que só se importa com Música recebe peça de Cinema → unsub. Backlog F-42. +- **Não há `signal` no app `newsletter`** — o signal vive em `apps.articles.signals.py:42-64` (cross-app reverso). Foi decisão para corrigir bug histórico C2 (double-email). Detalhe arquitetural surpreendente, documentado em CA04 abaixo. +- **Unsubscribe é POST com token no body, não GET com token na URL** — violação consciente da convenção 1-click clássica. Trade-off documentado em CA06. +- **SendGrid declarado em ADR-004 mas NÃO instalado** — produção usa Gmail SMTP. Migração = 3 env vars, sem código. +- **2 bugs invisíveis** quebram funcionalidade silenciosamente (BUG-1 cover URL relativa em emails; BUG-2 swallow exception mata retry). Hotfix antes de Sprint 8. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| -------------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------- | +| [RF-004](../../requirements/RF/RF-004-newsletter.md) | Cadastro + notificação por artigo + descadastro 1-clique | Realiza integralmente | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Throttle anon no subscribe; token UUID 122 bits não-enumerável | Realiza CA01, CA05 | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Opt-in explícito; link de unsub em todo email; direito ao esquecimento | Realiza CA07, CA09 | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Celery worker isola SMTP do request HTTP; subscribe sempre 200 | Realiza CA02 (assíncrono) | +| [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | Templates inline-CSS Outlook-safe + multipart com texto puro | Realiza templates de email | + +--- + +## Critérios de Aceitação (CAs) + +| ID | Critério | Como verificar | Status | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| **CA01** | Leitor (anônimo ou autenticado) cadastra email para newsletter via `POST /api/v1/newsletter/subscribe/` com body `{email}` e recebe 200 + mensagem amigável | `tests/test_views.py:test_subscribe_creates_subscriber` | ✅ | +| **CA02** | Sistema envia welcome email após subscribe via Celery task `send_welcome_email.delay(subscriber_id=...)` — view retorna sem esperar SMTP | `tests/test_views.py:test_subscribe_dispatches_welcome_email_task` | ✅ | +| **CA03** | Quando editor publica artigo (`Article.status` muda para `published`), sistema dispara `send_article_notification.delay(article_id=...)` que envia para **todos** os subscribers com `is_active=True` | `_dispatch_article_notification_sync` em `services.py:62-113` | ✅ | +| **CA04** | **Signal `post_save` de `Article` vive em `apps.articles.signals.py:42-64`, NÃO em `apps.newsletter.signals.py` (que NÃO existe).** Cross-app reverso (signal no produtor, não consumidor) é fix do bug histórico C2 que enviava 2 emails distintos por publicação | `apps/newsletter/apps.py:8-14` (sem `ready()` conectando signals) | ✅ (decisão arquitetural) | +| **CA05** | Email contém link único de unsubscribe com formato `${SITE_URL}/newsletter/cancelar/` — UUID4 com 122 bits entrópicos, não-enumerável sem signing extra | `services.py:33` (template `${SITE_URL}/newsletter/cancelar/{token}`) | ✅ | +| **CA06** | Unsubscribe via `POST /api/v1/newsletter/unsubscribe/` com `{token}` no body — **NÃO GET com token na URL**. Trade-off: token não vaza em logs/Referer/histórico browser; custo: requer página FE intermediária; **viola RFC 8058 `List-Unsubscribe-Post`** (Gmail não mostra botão nativo) | `views.py:UnsubscribeView` + DESIGN §3.1 trade-off documentado | ✅ (débito GAP-3) | +| **CA07** | Unsubscribe marca `is_active=False` via `update_fields=['is_active']` — preserva `unsubscribe_token` para re-subscribe futuro funcionar com o mesmo link da newsletter velha | `tests/test_views.py:test_subscribe_unsubscribe_resubscribe_full_cycle` | ✅ | +| **CA08** | `email` é único no DB via `EmailField(unique=True)` — subscribe duplicado **reativa** linha inativa em vez de criar nova (`get_or_create` no serializer) | `tests/test_views.py:test_subscribe_duplicate_email_returns_200_and_does_not_duplicate` + `test_subscribe_reactivates_inactive_subscriber` | ✅ | +| **CA09** | **LGPD opt-in explícito**: `subscribed_at` carimbado em `auto_now_add` como prova de consentimento. ⚠️ **Sem registro de IP, sem `consent_version`, sem texto exato apresentado** — débito L-01. Não há `unsubscribed_at` (débito L-02) | `models.py:6` (`subscribed_at = auto_now_add`) | 🟡 parcial (L-01/L-02) | +| **CA10** | **SendGrid declarado em ADR-004 mas NÃO instalado** — produção usa Gmail SMTP (`base.py:226` default `smtp.gmail.com`). Migração para SendGrid é troca de 3 env vars (`EMAIL_HOST`, `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD`) — sem mudança de código | INTEGRATIONS.md §SendGrid + DESIGN §1 tabela "Django Email backend" | 🟡 INT-1 (Sprint 8 candidato) | +| **CA11** | 🔴 **BUG-1 (hotfix candidato)**: `article.cover_image.url` é URL relativa em `article_notification.html:31`. Clientes de email NÃO resolvem contra `SITE_URL` — todos os subscribers recebem placeholder broken-image em TODA notificação. Test suite atual **não detecta** porque assert visual de URL absoluta inexiste | Arquivo `templates/newsletter/emails/article_notification.html:31` + observação 885 (May 29) | 🔴 invisível | +| **CA12** | 🔴 **BUG-2 (hotfix candidato)**: `send_welcome` em `services.py:58-59` faz `except Exception: return False` — **mata o `autoretry_for=(Exception,)` da Celery task** (`tasks.py:53`). Falhas SMTP nunca dão retry e ninguém sabe que welcome não saiu | `services.py:58` (try/except swallow) + comparação `tasks.py:50-71` | 🔴 invisível | + +--- + +## User Stories + +### US40.1 — Leitor anônimo cadastra email para receber newsletter + +> **Como** leitor anônimo do Interpop (sem conta no site) +> **Quero** digitar meu email no footer e receber um confirmação por email +> **Para** garantir que serei avisado de novas matérias sem precisar voltar manualmente. + +- **Prioridade**: 🔴 Imediato (canal owned — base de retenção) +- **Estimativa**: 5 Story Points +- **Sprint**: 2 (pre-busca) +- **Status**: ✅ Done +- **CAs cobertos**: CA01, CA02, CA05, CA08, CA09 +- **Persona**: leitor anônimo + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Cadastro de leitor anônimo na newsletter + Como leitor sem conta no site + Quero me inscrever na newsletter informando apenas meu email + Para receber novos artigos sem precisar voltar manualmente ao site + +Cenário: Inscrição inédita com email válido (caminho feliz) + Dado que estou no footer do site + E que ainda não estou inscrito na newsletter + Quando preencho "leitor@exemplo.com" no campo de email + E clico em "Inscrever" + Então vejo a mensagem "Inscrição realizada com sucesso!" + E uma nova linha em "newsletter_subscribers" é criada com is_active=True + E subscribed_at recebe o timestamp atual + E um unsubscribe_token UUID é gerado automaticamente + E uma task "send_welcome_email" é enfileirada via Celery + +Cenário: Inscrição com email já existente e ativo + Dado que "leitor@exemplo.com" já está inscrito com is_active=True + Quando preencho o mesmo email novamente + E clico em "Inscrever" + Então vejo a mensagem "E-mail já inscrito e reativado." + E nenhuma nova linha é criada (constraint unique respeitada) + E o welcome email é re-enviado mesmo assim + +Cenário: Reativação de inscrição cancelada anteriormente + Dado que "leitor@exemplo.com" estava com is_active=False (já tinha cancelado) + Quando preencho o mesmo email novamente + E clico em "Inscrever" + Então vejo a mensagem "E-mail já inscrito e reativado." + E is_active passa para True + E o unsubscribe_token original é preservado (mesmo UUID de antes) + E subscribed_at NÃO é atualizado (carimbo do opt-in original) + +Cenário: Email normalizado antes do lookup + Dado que estou no footer do site + Quando preencho " LEITOR@Exemplo.com " (com espaços e maiúsculas) + E clico em "Inscrever" + Então o email gravado no DB é "leitor@exemplo.com" (lowercase + trim) + E uma busca por "leitor@exemplo.com" encontra esse registro +``` + +--- + +### US40.2 — Subscriber recebe notificação automática de novo artigo publicado + +> **Como** leitor inscrito na newsletter (anônimo ou autenticado) +> **Quero** receber automaticamente um email cada vez que um novo artigo for publicado +> **Para** não perder nenhuma matéria sem precisar entrar no site todo dia. + +- **Prioridade**: 🔴 Imediato (razão de existir do canal) +- **Estimativa**: 8 Story Points +- **Sprint**: 3 (pre-busca) +- **Status**: ✅ Done — mas atinge ⚠️ **BUG-1 ativo** (imagem quebrada em produção) +- **CAs cobertos**: CA03, CA04, CA10, **CA11 (BUG-1 invisível)** +- **Persona**: leitor anônimo inscrito + leitor autenticado inscrito + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Notificação por artigo publicado + Como subscriber ativo + Quero receber email quando um artigo novo é publicado + Para acompanhar o Interpop sem entrar no site + +Cenário: Editor publica artigo novo (caminho feliz) + Dado que existem 47 subscribers com is_active=True na lista + E que um editor está editando um artigo com status="draft" + Quando o editor muda status para "published" e salva + Então o signal post_save em apps/articles/signals.py é disparado + E became_published é avaliado como True (transição draft→published) + E send_article_notification.delay(article_id=str(article.pk)) é chamado + E o request HTTP do editor retorna sem esperar SMTP (ADR-009) + E o worker Celery processa a task em background + E cada um dos 47 subscribers recebe um email com título, excerpt e cover + +Cenário: Editar artigo já publicado NÃO refaz fan-out (anti-double-email) + Dado que existe um artigo já com status="published" + E que existem 47 subscribers ativos + Quando o editor muda apenas o título do artigo e salva + Então o signal post_save é disparado + E became_published é avaliado como False (prev_status já era "published") + E send_article_notification.delay NÃO é chamado + E nenhum email novo é enviado a nenhum subscriber + +Cenário: Signal vive no app produtor, não no consumidor (decisão arquitetural) + Dado que estou inspecionando apps/newsletter/apps.py + Então NÃO encontro nenhum método ready() conectando signals + E apps/newsletter/signals.py NÃO existe + Quando inspeciono apps/articles/signals.py + Então encontro a função _notify_subscribers_on_publish nas linhas 42-64 + E essa função importa apps.newsletter.tasks.send_article_notification + +Cenário (BUG-1 ativo): Subscriber recebe email com imagem quebrada + Dado que sou subscriber ativo + E que produção usa MEDIA_URL relativa (ex: "/media/") + Quando um editor publica artigo novo com cover_image + E recebo a notificação no Gmail + Então o template renderiza (URL relativa) + E meu cliente de email NÃO resolve a URL contra interpop.com.br + E vejo placeholder broken-image no lugar da capa + E o test suite atual NÃO falha (sem assert de URL absoluta) + E ninguém é notificado do problema (silencioso em produção) +``` + +--- + +### US40.3 — Subscriber se descadastra via link único do email + +> **Como** subscriber que não quer mais receber a newsletter +> **Quero** clicar em um link único no email e ver confirmação de cancelamento +> **Para** parar de receber sem precisar logar no site nem responder ao remetente. + +- **Prioridade**: 🟠 Alta (LGPD compliance + boa prática de email) +- **Estimativa**: 5 Story Points +- **Sprint**: 2 (pre-busca) +- **Status**: ✅ Done +- **CAs cobertos**: CA05, CA06, CA07 +- **Persona**: subscriber ativo querendo cancelar + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Descadastro 1-clique via link do email + Como subscriber que quer cancelar + Quero clicar em um link único no email e confirmar o cancelamento + Para parar de receber a newsletter sem precisar logar nem responder + +Cenário: Cancelamento via link único (caminho feliz) + Dado que sou subscriber ativo com unsubscribe_token "abc-123-uuid" + Quando clico no link "https://interpop.com.br/newsletter/cancelar/abc-123-uuid" do email + E a página FE faz POST /api/v1/newsletter/unsubscribe/ {token: "abc-123-uuid"} + Então o backend valida o token via UnsubscribeSerializer.validate_token + E encontra o subscriber com is_active=True + E executa UPDATE is_active=False (update_fields=['is_active']) + E retorna 200 com "Inscrição cancelada com sucesso." + E o unsubscribe_token "abc-123-uuid" é PRESERVADO (não regenerado) + +Cenário: Double-unsubscribe retorna 400 amigável (não 500) + Dado que sou subscriber JÁ cancelado (is_active=False) + Quando clico no mesmo link de cancelamento de novo + E a página FE faz POST /api/v1/newsletter/unsubscribe/ {token: "abc-123-uuid"} + Então o backend filtra is_active=True na query + E não encontra o subscriber (já cancelado) + E retorna 400 com mensagem "Token inválido ou já cancelado" + E o erro NÃO é 500 (UX amigável mesmo em re-tentativa) + +Cenário: Token preservado permite re-subscribe completo + Dado que sou subscriber cancelado (is_active=False, token "abc-123-uuid") + Quando faço novo subscribe com o mesmo email no footer + Então is_active volta para True + E meu unsubscribe_token continua "abc-123-uuid" (PRESERVADO) + E o link da newsletter velha que recebi 6 meses atrás ainda funciona + +Cenário (RFC 8058 ausente — GAP-3): Gmail não mostra botão nativo de cancelar + Dado que estou abrindo o email da newsletter no Gmail + Quando o Gmail busca por List-Unsubscribe-Post: List-Unsubscribe=One-Click + Então o header NÃO está presente nos emails do Interpop + E o Gmail NÃO mostra o botão "Cancelar inscrição" na barra superior + E meu reputation score como remetente é prejudicado + E o UX é pior comparado a newsletters que implementam RFC 8058 +``` + +--- + +## Tasks (implementação) + +### Tasks US-bound retroativas (todas ✅ Done — Sprint 2-3, pre-busca) + +| ID | Descrição | Prioridade | Status | Sprint | +| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------ | ------ | +| T40.1.1 | Bootstrap do Django app `apps.newsletter` (apps.py, urls.py, admin.py, estrutura de pastas) | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.1.2 | Model `NewsletterSubscriber` (`email unique`, `subscribed_at auto_now_add`, `is_active default=True`, `unsubscribe_token UUIDField default=uuid.uuid4`) + Meta `db_table='newsletter_subscribers'` + index composto `(email, is_active)` | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.1.3 | Migration `0001_initial.py` — schema completo de `newsletter_subscribers` | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.1.4 | `SubscribeSerializer` com normalização `lower().strip()` em `validate_email` + `get_or_create` no save + reativação de inativo preservando token | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.1.5 | `SubscribeView` (APIView + AllowAny + AnonRateThrottle) chamando `send_welcome_email.delay(subscriber_id=str(pk))` | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.1.6 | Celery task `send_welcome_email` com `autoretry_for=(Exception,)` + `retry_backoff_max=300` + `max_retries=3` | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.1.7 | Service `send_welcome(subscriber)` em `services.py` (carrega template, monta `EmailMultiAlternatives` multipart, dispara) | 🟠 | ✅ Done (Sprint 2-3, pre-busca) ⚠️ **contém BUG-2** | 2 | +| T40.1.8 | Templates `templates/newsletter/emails/welcome.html` + `welcome.txt` + `base.html` (layout inline-CSS Outlook-safe) | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.1.9 | URLs `subscribe/` + `unsubscribe/` em `apps/newsletter/urls.py` + inclusão em `config/urls.py` sob `/api/v1/newsletter/` | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.2.1 | Celery task `send_article_notification(article_id)` com recarregamento por `pk` + `autoretry_for=(Exception,)` + `max_retries=2` (menos crítico que welcome) | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 3 | +| T40.2.2 | Signal `post_save` em `apps/articles/signals.py:42-64` (cross-app reverso — produtor) + guard `became_published = created OR prev_status != PUBLISHED` para evitar refan-out | 🔴 | ✅ Done (Sprint 2-3, pre-busca) — fix bug C2 histórico | 3 | +| T40.2.3 | Service `_dispatch_article_notification_sync(article, subscribers=None)` em `services.py:62-113` (loop síncrono — chamar só de task) | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | 3 | +| T40.2.4 | Templates `article_notification.html` + `article_notification.txt` com título, excerpt, cover_image, link canônico, link unsub | 🟠 | ✅ Done (Sprint 2-3, pre-busca) ⚠️ **contém BUG-1** | 3 | +| T40.2.5 | Admin action `resend_notification` em `apps/articles/admin.py:30-50` (fallback editorial p/ reenvio manual em SMTP outage; usa `.delay()`) | 🟡 | ✅ Done (Sprint 2-3, pre-busca) | 3 | +| T40.3.1 | `UnsubscribeSerializer` com `validate_token` filtrando `is_active=True` + `update_fields=['is_active']` (preserva token) | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.3.2 | `UnsubscribeView` (APIView + AllowAny) chamando serializer (200 sucesso · 400 já cancelado/token inválido) | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | 2 | +| T40.4.1 | Suite `tests/test_views.py` — 13 testes E2E batendo DB + Celery EAGER (subscribe inédito, duplicado, reativa, normaliza, welcome enqueue, unsub feliz, double-unsub, full cycle subscribe→unsub→resubscribe com token preservado) | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | 2-3 | +| T40.4.2 | Apps.py SEM `ready()` conectando signals (decisão consciente após bug C2) + comment explicativo `apps.py:8-14` | 🟠 | ✅ Done (Sprint 2-3, pre-busca) | 3 | + +### Tasks transversais (TX) — retroativas (✅ Done Sprint 2-3, pre-busca) + +| ID | Descrição | Prioridade | Status | +| ----- | --------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------- | +| TX-40 | Config Celery base + Redis broker em `config/settings/base.py` (ADR-009) — pré-requisito para tasks async | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | +| TX-41 | Config `EMAIL_BACKEND` + `EMAIL_HOST` em `base.py:226` (default `smtp.gmail.com`) + flag `USE_REAL_EMAIL` | 🔴 | ✅ Done (Sprint 2-3, pre-busca) | + +### Hotfix candidatos (descobertos em DESIGN retroativo 2026-06-09 — NÃO entram em Sprint 8) + +| ID | Descrição | Prioridade | Status | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------ | +| **HOTFIX-1 (BUG-1)** | Fix `cover_image.url` relativa em `article_notification.html:31` — passar `absolute_url` no contexto da task OU settear `MEDIA_URL` absoluta em `production.py` | 🔴 Imediato | ⏳ Pending (recomendado antes de Sprint 8) | +| **HOTFIX-2 (BUG-2)** | Remover `except Exception: return False` em `services.py:58-59` — deixar service propagar exceções pra `autoretry_for` da Celery task funcionar | 🔴 Imediato | ⏳ Pending (recomendado antes de Sprint 8) | +| HOTFIX-3 | Adicionar test de regressão para anti-double-fan-out (I5 do DESIGN — editar artigo publicado NÃO deve refan-out) | 🟠 Alta | ⏳ Pending (GAP-4 do DESIGN) | + +--- + +## Open Questions (para roadmap pós-F-40) + +> Cada item abaixo é uma decisão pendente que afeta o produto. **Não delego de volta — recomendo a ordem abaixo**: + +| # | Pergunta | Recomendação | Bloqueio | +| --- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| 1 | **BUG-1 — cover URL relativa quebra imagem em emails reais** | 🔴 Hotfix imediato. Investigar primeiro se `MEDIA_URL` em `production.py` é absoluta — possivelmente fix de 1 linha em settings (não em template). | Nenhum | +| 2 | **BUG-2 — `except Exception` em `send_welcome` mata `autoretry_for` da task** | 🔴 Hotfix imediato. Remover try/except do service; deixar exceção propagar pra task. UI já lida com 200 mesmo se SMTP falhar. | Nenhum | +| 3 | **Quando migrar Gmail SMTP → SendGrid real?** | Sprint 8 (junto com F-41 bounce handling). Gmail teto 500/dia já é ceil rígido. SendGrid free tier 100/dia cobre até ~80 publicações/dia + desbloqueia webhooks de bounce/open | Criar conta SendGrid + configurar DNS (SPF/DKIM/DMARC) | +| 4 | **GET unsubscribe RFC 8058 para Gmail mostrar botão nativo de cancelar (GAP-3)?** | Sprint 8 ou antes. Adicionar header `List-Unsubscribe-Post: List-Unsubscribe=One-Click` é 4 linhas em `services.py`. Endpoint POST `/api/v1/newsletter/unsubscribe-oneclick/?token=...` (sem CSRF, AllowAny) — Gmail manda direto sem precisar de página FE | Mínimo — concebível em 1 PR pequeno | +| 5 | **Adicionar `unsubscribed_at` + `consent_version` + `consent_ip` (L-01/L-02)?** | Sprint 8 (migration custo < 10min; benefício compliance imediato + permite métricas churn). Pode entrar com hotfix 1/2. | Nenhum | +| 6 | **Bounce handling via webhook SendGrid (GAP-1)?** | Bloqueado por #3. Quando SendGrid plugado, webhook é 1 endpoint POST que faz `is_active=False` + grava `bounce_reason` | Depende de #3 | +| 7 | **Open rate tracking via pixel (GAP-5)?** | Sprint 8 **APÓS** modelo de opt-in granular LGPD (L-01 resolvido em #5). Sem opt-in separado, pixel viola LGPD | Depende de #3 + #5 | +| 8 | **Substituir PK `BigAutoField` por `UUIDField` para coerência arquitetural?** | ⚪ Baixa prioridade. Migration custosa, FKs cross-app inexistentes ajudam, mas churn não justifica hoje | Nenhum | +| 9 | **Cleanup policy de inativos (deletar `is_active=False` após N meses)?** | Definir junto com modelo LGPD do projeto (retenção declarada). Sem ADR ainda — bloqueio é decisão de produto, não técnico | Decisão editorial | + +--- + +## Definition of Done — verificação + +- [x] CA01–CA10 verificados em código (em produção desde Sprint 3) +- [x] CA11–CA12 documentados como bugs invisíveis (não falham test suite atual — invisibilidade é parte do problema) +- [x] US40.1, US40.2, US40.3 com cenários BDD descritos (cenários BDD retroativos — código sem fixture explícita; 13 testes E2E em `tests/test_views.py` cobrem CAs implicitamente) +- [x] Todas as Tasks T40.X.X retroativas marcadas Done com nota "(Sprint 2-3, pre-busca)" +- [x] Code-review aprovado historicamente (sem PR específico — implementação inicial pré-processo de review formal) +- [x] Cobertura backend ≥ 85% no módulo (`apps/newsletter/`) via 13 testes E2E +- [x] Documentação cruzada atualizada — RF-004 + EP-04 + DESIGN.md citam esta Feature +- [x] Em produção via deploy contínuo (Sprint 3 → presente, sem regressão histórica conhecida além de BUG-1/BUG-2) +- [ ] **HOTFIX-1 / HOTFIX-2 pendentes** — recomendados antes de Sprint 8 + +**Status final**: ✅ **Done em produção** — mas com 2 bugs invisíveis recomendados pra hotfix imediato + 9 open questions pra roadmap pós-F-40. + +--- + +## Specs técnicas relacionadas + +- [DESIGN.md módulo newsletter](../../specs/newsletter/DESIGN.md) — 9 seções: stack, data model, contract, fluxos críticos (Mermaid), invariantes, runbook ops, débitos, cross-refs, open questions +- [INTEGRATIONS.md §SendGrid](../../specs/codebase/INTEGRATIONS.md) — config real Gmail vs. declarada SendGrid (INT-1) +- [CONCERNS.md](../../specs/codebase/CONCERNS.md) — INT-1 (SendGrid não usado), L-01 (sem proof granular), L-02 (sem `unsubscribed_at`), GAP-1/3/4/5 + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-004](../../requirements/RF/RF-004-newsletter.md), [RNF-security](../../requirements/RNF/RNF-security.md), [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md), [RNF-availability](../../requirements/RNF/RNF-availability.md), [RNF-a11y](../../requirements/RNF/RNF-a11y.md) | +| ↑ Epic pai | [EP-04 Newsletter e comunicação](../epics/EP-04-newsletter-comunicacao.md) | +| → Sprint(s) | Sprint 2-3 (implementação original, pre-busca) · documentação retroativa em chore/docs-reorg (PR #39, 2026-06-09) | +| → Specs técnicas | [DESIGN.md newsletter](../../specs/newsletter/DESIGN.md) + ADRs ADR-001, ADR-004, ADR-009, ADR-010 | +| → Features filhas | n/a (F-40 é Feature, não Epic) | +| ← Features irmãs sob EP-04 | F-41 Bounce handling + open rate (⏳ Sprint 8) · F-42 Segmentação por editoria (⏳ Sprint 8) · F-43 A/B subject lines (⏳ Sprint 8+) | + +--- + +_F-40 ✅ Done em produção desde Sprint 3. Documentação retroativa concluída em 2026-06-09 (chore/docs-reorg). **Próxima ação recomendada**: aplicar HOTFIX-1 + HOTFIX-2 antes de abrir Sprint 8 com F-41 — esses 2 bugs invisíveis quebram funcionalidade silenciosamente e degradam reputação de remetente cada dia que passa em produção._ diff --git a/docs/backlog/features/F-50-ban-banrequest-workflow.md b/docs/backlog/features/F-50-ban-banrequest-workflow.md new file mode 100644 index 00000000..8eabba85 --- /dev/null +++ b/docs/backlog/features/F-50-ban-banrequest-workflow.md @@ -0,0 +1,329 @@ +# F-50 — Ban + BanRequest workflow + +> **Tipo**: Feature +> **Epic pai**: [EP-05 Moderação da comunidade](../epics/EP-05-moderacao-comunidade.md) +> **Sprint de execução**: Sprint 2-3 (mai/2026, pre-busca) +> **Status**: ✅ Done (retroativo — Sprint 2-3) · 📝 Documentação retroativa Sprint 5 (2026-06-09) +> **Prioridade**: 🔴 Imediato (fundação da seção de comentários abertos) + +--- + +## Descrição (visão de produto) + +Admin entra no painel de moderação, identifica leitor que violou termos de comunidade (spam, discurso de ódio, ataque pessoal a redator) e bana direto com uma justificativa formal — efeito imediato na próxima requisição do banido. Em paralelo, editor que viu padrão sutil de ataque (que pede 2º par de olhos) abre **solicitação de banimento** com motivo e cópia da mensagem ofensiva — a solicitação fica em fila e admin decide aprovar (cria ban real) ou rejeitar (encerra sem efeito). Hierarquia inegociável `dev > admin > editor > user` é sustentada em 3 camadas independentes: queryset filtrado por ator, validação no serializer, barreira final na camada de serviço. + +Esta Feature é a **fundação** do EP-05 (Moderação da Comunidade) — Features futuras (F-51 notificação email, F-52 fluxo de contestação, F-53 auto-expiração) constroem em cima dela. + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| -------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------- | +| [RF-003](../../requirements/RF/RF-003-moderation.md) | Ban direto + BanRequest com invariantes de hierarquia | Realiza diretamente | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Defesa em 3 camadas; permissões DRF; trilha de auditoria | Realiza CA04-CA07/CA09 | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | Falha do worker celery (email) não bloqueia abertura de `BanRequest` | Realiza degradação graciosa | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Razão do ban (dado pessoal sensível) com retenção 5 anos | Trilha em audit log | + +--- + +## Critérios de Aceitação (CAs) + +| ID | Critério | Como verificar | Status | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -------- | +| **CA01** | Admin aplica Ban direto via `POST /api/v1/moderation/bans/` com `target_user_id` + `reason` — sistema cria row em `bans`, atualiza `User.is_banned=True` e grava `AuditLog` | `test_services.py::test_ban_user_creates_ban_and_flips_flag` | ✅ | +| **CA02** | Editor abre BanRequest via `POST /api/v1/moderation/ban-requests/` com `target` + `reason` — sistema cria row com `status=pending`, dispara task celery que notifica admins por email | `test_services.py` + `test_tasks.py::test_notify_admins` | ✅ | +| **CA03** | Admin decide BanRequest via `POST /api/v1/moderation/ban-requests//decide/` com `action='approve'` cria Ban e `action='reject'` fecha o BanRequest com `status=rejected` | `test_services.py::test_approve_ban_request` + `test_reject` | ✅ | +| **CA04** | **Invariante I1**: dev NUNCA pode ser banido — bloqueio em **3 camadas** (queryset exclui dev; `validate_user_id` rejeita; `services.ban_user` lança `PermissionDenied`) | `test_ban_hierarchy.py::test_dev_immune_three_layers` | ✅ | +| **CA05** | **Invariante I2**: admin pode banir editor/user, mas NÃO outro admin — bloqueio em 3 camadas idêntico | `test_ban_hierarchy.py::test_admin_cannot_ban_admin` | ✅ | +| **CA06** | **Invariante I3**: editor pode abrir BanRequest contra user — NÃO contra admin nem dev — queryset `role__in=['user','editor']` filtra; `validate_target_id` reforça | `test_serializers.py::test_editor_target_filtered` | ✅ | +| **CA07** | **Invariante I4**: auto-target em BanRequest (`requested_by == target_user`) retorna **400 Bad Request** com `{"target_id": "Não é permitido abrir solicitação contra si mesmo"}` | `test_serializers.py::test_self_target_400` (gap GAP-1 — Sprint 6) | 🟡 gap | +| **CA08** | BanRequest pending dura **indefinidamente** até admin decidir — sem TTL automático. Débito documentado como GAP-2 do DESIGN — auto-rejeição por cron é F-53 futuro | DESIGN.md §7 GAP-2 | ⚠️ doc'd | +| **CA09** | AuditLog é **INSERT-only** (sem update, sem delete): `actor` + `action ∈ {'ban', 'ban_request_open', 'ban_request_decide'}` + `target` + `ip` + `user_agent` + `created_at` | `test_audit_middleware.py` em `apps.audit` | ✅ | +| **CA10** | **(DESIGN OPS-1)** `Ban.user` é `OneToOneField` — re-banir o mesmo user via `update_or_create` **sobrescreve** `banned_by`, `reason` e `trigger_message` originais (perde histórico do ciclo anterior). Trade-off doc'd em `services.py:22-31`. | DESIGN.md §2.2 + comentário no código | ⚠️ doc'd | +| **CA11** | **(DESIGN OPS-2)** Sistema NÃO invalida JWT do banido ao aplicar ban — banido mantém sessões ativas com permissão de **leitura** até o token de acesso expirar (~30min). Escrita (POST/DELETE) é bloqueada imediatamente por `IsNotBanned`. | DESIGN.md §7 OPS-3 | ⚠️ doc'd | +| **CA12** | **(DESIGN OPS-3)** Sistema NÃO envia email ao banido (nem ao aprovar BanRequest nem ao aplicar ban direto). Banido descobre tentando comentar. Resolução: F-51 (Sprint 8). | DESIGN.md §7 OPS-2 | ⚠️ doc'd | + +> **Hotfix candidato (mais sério dos três)**: CA11 (OPS-3 — sem JWT invalidation imediato). Em janela de até 30min, ban "aplicado" não tem efeito real para leitura autenticada. Atacante bana hoje continua navegando até o token vencer. Tratativa requer ADR sobre estratégia (blocklist Redis vs. TTL curto). Mais sério que CA10 (perda de histórico — incomoda audit, não compromete segurança) e CA12 (UX — incomoda banido, não compromete sistema). + +--- + +## User Stories + +### US50.1 — Admin aplica ban direto contra leitor com violação clara + +> **Como** admin do Interpop +> **Quero** banir diretamente um leitor que cometeu violação clara dos termos +> **Para** proteger a seção de comentários sem latência de fluxo formal. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 5 Story Points +- **Sprint**: 2 +- **Status**: ✅ Done (Sprint 2-3, pre-busca) +- **CAs cobertos**: CA01, CA04, CA05, CA09, CA10, CA11 +- **Persona**: [admin](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Banimento direto aplicado por admin + Como admin do Interpop + Quero banir leitor com violação clara + Para proteger a comunidade sem latência + +Cenário: Ban direto aplicado com sucesso contra leitor (caminho feliz) + Dado que sou admin autenticado + E que existe leitor "alvo@x.com" com role="user" e is_banned=False + Quando faço POST em "/api/v1/moderation/bans/" com user_id do alvo e reason="spam reincidente" + Então recebo HTTP 201 com o objeto Ban serializado + E a row em "bans" existe com is_active=True e banned_by=meu_id + E o campo is_banned do User alvo agora é True + E a trilha de auditoria registra action="ban" + actor + target + ip + user_agent + E o response retorna em ≤ 300ms p95 + +Cenário: Admin tenta banir outro admin → 403 + Dado que sou admin autenticado (não dev) + E que existe outro admin "outro-admin@x.com" com role="admin" + Quando faço POST em "/api/v1/moderation/bans/" com user_id do outro admin + Então recebo HTTP 400 (queryset filtrado — camada 1 da defesa) + E o body contém {"user_id": "Selecione uma opção válida"} + E nenhuma row foi criada em "bans" + E o is_banned do outro admin permanece False + +Cenário: Tentativa de banir dev → 403 (invariante I1) + Dado que sou admin autenticado + E que existe dev "gabriel@interpop.com" com role="dev" + Quando faço POST em "/api/v1/moderation/bans/" com user_id do dev + Então recebo HTTP 400 (queryset não inclui dev — camada 1) + E mesmo se a camada 1 falhasse, validate_user_id rejeitaria (camada 2) + E mesmo se a camada 2 falhasse, services.ban_user lançaria PermissionDenied (camada 3) + E o is_banned do dev permanece False sob qualquer cenário de bug + +Cenário: Re-banir mesmo user (OPS-1 — perde histórico) + Dado que existe Ban inativo de "alvo@x.com" criado por admin_A com reason="ofensa 1" + E que sou admin_B autenticado + Quando faço POST em "/api/v1/moderation/bans/" com user_id de "alvo@x.com" e reason="ofensa 2" + Então o sistema usa update_or_create no Ban OneToOne + E a row em "bans" agora tem banned_by=admin_B e reason="ofensa 2" + E o histórico de admin_A com reason="ofensa 1" é PERDIDO (trade-off OPS-1 doc'd) + E a trilha de auditoria preserva ambos os ciclos (audit_log é insert-only) +``` + +--- + +### US50.2 — Editor solicita banimento com justificativa formal + +> **Como** editor do Interpop +> **Quero** abrir solicitação formal de banimento contra leitor +> **Para** registrar o caso com motivo e mensagem ofensiva, e que admin decida com 2º par de olhos. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 5 Story Points +- **Sprint**: 2 +- **Status**: ✅ Done (Sprint 2-3, pre-busca) +- **CAs cobertos**: CA02, CA06, CA07, CA09 +- **Persona**: [editor](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Solicitação de banimento aberta por editor + Como editor do Interpop + Quero registrar solicitação formal com justificativa + Para que admin decida com contexto e trilha de auditoria + +Cenário: Editor abre BanRequest contra leitor (caminho feliz) + Dado que sou editor autenticado + E que existe leitor "alvo@x.com" com role="user" + Quando faço POST em "/api/v1/moderation/ban-requests/" com target_id, reason e trigger_message + Então recebo HTTP 201 com o BanRequest serializado + E a row em "ban_requests" existe com status="pending" e requested_by=meu_id + E o sinal post_save dispara a task celery notify_admins_on_new_ban_request + E ao menos 1 admin recebe email "[Interpop] Nova solicitação de banimento" em ≤ 5min + +Cenário: Editor tenta banir dev → 400 (invariante I3) + Dado que sou editor autenticado + E que existe dev "gabriel@interpop.com" com role="dev" + Quando faço POST em "/api/v1/moderation/ban-requests/" com target_id do dev + Então recebo HTTP 400 (queryset role__in=["user","editor"] não inclui dev) + E o body contém {"target_id": "Selecione uma opção válida"} + E nenhuma row foi criada em "ban_requests" + +Cenário: Editor tenta banir admin → 400 (invariante I3) + Dado que sou editor autenticado + E que existe admin "admin@interpop.com" com role="admin" + Quando faço POST em "/api/v1/moderation/ban-requests/" com target_id do admin + Então recebo HTTP 400 (queryset filtra admin fora) + E o body contém {"target_id": "Selecione uma opção válida"} + +Cenário: Editor tenta auto-target (requested_by == target) → 400 (CA07, GAP-1) + Dado que sou editor autenticado + Quando faço POST em "/api/v1/moderation/ban-requests/" com target_id=meu_próprio_id + Então recebo HTTP 400 + E o body contém {"target_id": "Não é permitido abrir solicitação contra si mesmo"} + E nenhuma row foi criada em "ban_requests" + +Cenário: Worker celery down — falha no envio de email não bloqueia abertura + Dado que sou editor autenticado + E que o broker Redis está indisponível (celery enqueue falha) + Quando faço POST em "/api/v1/moderation/ban-requests/" com payload válido + Então a row em "ban_requests" é criada com sucesso (HTTP 201) + E o signal captura a exceção do enqueue e loga warning + E o admin pode visualizar a solicitação via "/admin/moderation/banrequest/" mesmo sem email +``` + +--- + +### US50.3 — Admin revisa fila e decide solicitação de banimento + +> **Como** admin do Interpop +> **Quero** revisar fila de BanRequests pendentes e decidir aprovar ou rejeitar +> **Para** aplicar banimento com contexto editorial ou encerrar caso infundado. + +- **Prioridade**: 🔴 Imediato +- **Estimativa**: 8 Story Points +- **Sprint**: 3 +- **Status**: ✅ Done (Sprint 2-3, pre-busca) +- **CAs cobertos**: CA03, CA04, CA05, CA08, CA09 +- **Persona**: [admin](../../requirements/personas-e-cenarios.md) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Decisão de solicitação de banimento por admin + Como admin do Interpop + Quero aprovar ou rejeitar BanRequest com trilha de auditoria + Para que decisões editoriais fiquem registradas e a hierarquia seja respeitada + +Cenário: Admin aprova BanRequest → Ban criado + AuditLog gravado (CA03) + Dado que sou admin autenticado + E que existe BanRequest com status="pending" contra "alvo@x.com" + Quando faço POST em "/api/v1/moderation/ban-requests//decide/" com action="approve" e decision_note + Então recebo HTTP 200 com o BanRequest atualizado + E o BanRequest agora tem status="approved", decided_by=meu_id, decided_at preenchido + E uma row em "bans" foi criada via services.approve_ban_request → ban_user + E o User alvo agora tem is_banned=True + E a trilha de auditoria registra action="ban_request_decide" + decision="approve" + E a transação é atômica (BanRequest + Ban + User.is_banned coerentes ou tudo rollback) + +Cenário: Admin rejeita BanRequest → nenhum Ban criado + Dado que sou admin autenticado + E que existe BanRequest com status="pending" + Quando faço POST em "/api/v1/moderation/ban-requests//decide/" com action="reject" e decision_note + Então recebo HTTP 200 + E o BanRequest agora tem status="rejected", decided_by=meu_id, decided_at preenchido + E NENHUMA row foi criada em "bans" + E o User alvo permanece com is_banned=False + E a trilha de auditoria registra action="ban_request_decide" + decision="reject" + +Cenário: Decisão é idempotente — re-aprovar não duplica Ban (invariante I6) + Dado que existe BanRequest já approved (status="approved", Ban ativo existe) + E que sou admin autenticado + Quando chamo services.approve_ban_request novamente + Então o sistema faz early-return retornando o Ban ativo existente + E NENHUMA nova row em "bans" é criada + E o status do BanRequest permanece "approved" sem alteração + +Cenário: BanRequest em estado terminal não regrede (invariante I7) + Dado que existe BanRequest com status="rejected" + E que sou admin autenticado + Quando faço POST em "/decide/" com action="approve" + Então recebo HTTP 400 ou 409 (BanRequestDecideView exige status=="pending") + E o status permanece "rejected" + +Cenário: Editor vê só seus próprios BanRequests; admin vê todos (invariante I8) + Dado que existem BanRequests criados por editor_A e por editor_B + Quando editor_A faz GET em "/api/v1/moderation/ban-requests/" + Então recebe somente as solicitações que ele próprio criou + Quando admin faz o mesmo GET + Então recebe TODAS as solicitações (editor_A, editor_B, históricos) +``` + +--- + +## Tasks (implementação) + +### Tasks US-bound (T50.X.X — todas ✅ Done Sprint 2-3, pre-busca) + +| ID | Descrição | Prioridade | Sprint | Status | +| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------ | ------------------------------- | +| T50.1.1 | Bootstrap do Django app `apps.moderation` (estrutura `models.py`, `serializers.py`, `services.py`, `views.py`, `urls.py`, `signals.py`, `tasks.py`, `admin.py`, `tests/`) | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.1.2 | Modelo `Ban` com `OneToOneField(user)`, `banned_by`, `reason`, `trigger_message`, `is_active`, `unbanned_by`, `unbanned_at`, `expires_at` (nullable, sem job) | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.1.3 | Modelo `BanRequest` com `target`, `requested_by`, `reason`, `trigger_message`, `status` (PENDING/APPROVED/REJECTED), `decided_by`, `decided_at`, `decision_note` | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.1.4 | Migration `0001_initial` — schema base de `Ban` + `BanRequest` com índices (`status, -created_at`) e (`is_active, -created_at`) | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.1.5 | Service layer — `ban_user()`, `unban_user()`, `approve_ban_request()`, `reject_ban_request()` (todos `@transaction.atomic` exceto reject) | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.1.6 | Serializers — `BanSerializer` e `BanRequestSerializer` com queryset **ator-aware** (`user_id`/`target_id` filtrado por role do request.user) | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.2.1 | Views — `BanListCreateView`, `BanDestroyView`, `BanRequestListCreateView`, `BanRequestDecideView` (decide é `POST /decide/`, não PATCH) | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.2.2 | URLs em `apps.moderation.urls` montadas em `/api/v1/moderation/bans/` e `/api/v1/moderation/ban-requests/` (ADR-010) | 🟠 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.2.3 | Permissões reusáveis em `apps.users.permissions` (`IsAdminUser`, `IsEditorOrAdmin`, `IsNotBanned`) aplicadas em todas as views | 🔴 | 2 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.3.1 | Signal `post_save` em `BanRequest` (created=True AND status=PENDING) → enfileira `notify_admins_on_new_ban_request.delay(id)` com try/except | 🟠 | 3 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.3.2 | Task celery `notify_admins_on_new_ban_request(ban_request_id)` com autoretry exponencial (max_retries=3, backoff até 5min); inclui role=DEV na lista de destinatários | 🟠 | 3 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.3.3 | AuditLog via middleware HTTP em `apps.audit` (sem import direto) — captura `ban`, `ban_request_open`, `ban_request_decide` por método + status code | 🟠 | 3 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.4.1 | Admin Django — `Ban` writable (caminho de fuga emergência); `BanRequest` **fully read-only** (`has_add/change/delete_permission → False`) para forçar decisão via API | 🟠 | 3 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.5.1 | Testes — `test_ban_hierarchy.py` (invariantes I1/I2/I10 nas 3 camadas) | 🔴 | 3 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.5.2 | Testes — `test_serializers.py` (queryset ator-aware + validação) | 🔴 | 3 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.5.3 | Testes — `test_services.py` (idempotência I6 + atomicidade I5 + estado terminal I7) | 🔴 | 3 | ✅ Done (Sprint 2-3, pre-busca) | +| T50.5.4 | Testes — `test_tasks.py` (envio de email com retry + recarga por ID) | 🟠 | 3 | ✅ Done (Sprint 2-3, pre-busca) | + +### Tasks transversais (TX-NN) + +| ID | Descrição | Prioridade | Status | +| ----- | ------------------------------------------------------------------------------------------ | ---------- | ------------------------ | +| TX-50 | Documentar permissões reusáveis em `apps.users.permissions` (single source of truth — C14) | 🟠 | ✅ Done (CONVENTIONS.md) | +| TX-51 | DESIGN.md retroativo do módulo `moderation` | 🟠 | ✅ Done 2026-06-09 | +| TX-52 | RF-003 retroativo (substitui stub) | 🟠 | ✅ Done 2026-06-09 | +| TX-53 | EP-05 retroativo (substitui stub) | 🟠 | ✅ Done 2026-06-09 | + +--- + +## Definition of Done — verificação + +- [x] CA01-CA06, CA09 verificados por testes automatizados (`apps/moderation/tests/`) +- [x] CA07 verificado parcialmente — bloqueio implícito via `is_immune_to_ban` quando target é admin/dev; check explícito `target != requested_by` (GAP-1) **pendente** para Sprint 6 (2 LOC + 1 teste) +- [x] CA08, CA10, CA11, CA12 documentados como trade-offs em DESIGN §7 (OPS-1, OPS-2, OPS-3) + Open Questions §9 +- [x] US50.1, US50.2, US50.3 com cenários BDD escritos (este arquivo) +- [x] Todas as Tasks 🔴 Imediate Done (Sprint 2-3, pre-busca) +- [x] Code-review aprovado (Sprint 2-3) +- [x] Cobertura backend ≥ 85% local em `apps.moderation/tests/` +- [x] Documentação cruzada atualizada — RF-003 cita F-50, EP-05 lista, DESIGN.md cross-ref completo +- [x] Mergeada em `main` via PRs Sprint 2-3 (pre-busca) + +**Status final**: ✅ **Done** com CA07 marcado como gap menor (GAP-1) e CA08/CA10/CA11/CA12 documentados como trade-offs aceitos com Open Questions futuras. + +--- + +## Open Questions (cross-ref DESIGN §9) + +1. **F-51 — Notificação por email do banido** (OPS-2): banido descobre tentando comentar. LGPD pede transparência sobre processamento. Backlog Sprint 8. +2. **F-52 — Fluxo de contestação do banido (appeal)** (GAP-5): endpoint `POST /api/v1/moderation/bans//appeal/` com `appeal_message` e status `APPEALED`. Aceitável até volume justificar. +3. **F-53 — Auto-expiração de banimento temporário** (OPS-1): cron + service que respeita `Ban.expires_at`. Depende de ADR sobre política (TTL default? per-ban TTL?). Sem decisão. +4. **Migration futura — `Ban.user` de OneToOne para FK + UniqueConstraint(condition=Q(is_active=True))** (GAP-4 → CA10): preserva histórico individual de cada ciclo `ban → unban → re-ban`. Migrar quando houver dor real de auditoria. +5. **JWT invalidation imediato em ban** (OPS-3 → CA11 — **hotfix candidato**): `services.ban_user` deve revogar sessões ativas? Opções: (a) blocklist JWT em Redis (custo: Redis no caminho de cada request autenticado), (b) reduzir TTL do JWT para 5min com refresh agressivo, (c) aceitar leitura banida até token expirar (status quo). Decisão arquitetural — virar ADR antes de implementar. +6. **Auto-rejeição de BanRequest pendente há > 7 dias** (GAP-2): cron diário que vira `status="rejected"` com `decision_note="expirada"`. Define SLA primeiro. +7. **Rate-limit em `BanRequest` create** (GAP-3): editor mal-intencionado pode floodar admins de email. Aplicar `ScopedRateThrottle('moderation_request')`. + +--- + +## Specs técnicas relacionadas + +- [DESIGN.md retroativo de `moderation`](../../specs/moderation/DESIGN.md) — fonte de verdade (defesa 3 camadas, 10 invariantes, 9 débitos) +- [Improvement-system.md §11.6 S8 + C13](../../planning/Improvement-system.md) — origem da defesa em 3 camadas +- [CONCERNS.md](../../specs/codebase/CONCERNS.md) — débitos análogos +- [CONVENTIONS.md](../../specs/codebase/CONVENTIONS.md) — permissions reusáveis em `apps.users.permissions` + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-003](../../requirements/RF/RF-003-moderation.md), [RNF-security](../../requirements/RNF/RNF-security.md), [RNF-availability](../../requirements/RNF/RNF-availability.md), [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | +| ↑ Epic pai | [EP-05](../epics/EP-05-moderacao-comunidade.md) | +| → Sprint(s) | Sprint 2-3 (entrega pre-busca), Sprint 5 (documentação retroativa 2026-06-09), Sprint 8 (F-51 previsto) | +| → Specs técnicas | [DESIGN.md](../../specs/moderation/DESIGN.md) | +| → Features filhas | n/a (F-50 é Feature, não Epic) | +| ← Features irmãs sob EP-05 | F-51 (notificação email — Sprint 8), F-52 (appeal — sem sprint), F-53 (auto-expire — sem sprint) | + +--- + +_F-50 ✅ Done — entregue em Sprint 2-3 (pre-busca). Documentação retroativa concluída 2026-06-09 (Sprint 5). Próxima ação no Epic: F-51 (notificação email) no Sprint 8._ diff --git a/docs/backlog/features/F-60-observability-audit-trail.md b/docs/backlog/features/F-60-observability-audit-trail.md new file mode 100644 index 00000000..6f5304e0 --- /dev/null +++ b/docs/backlog/features/F-60-observability-audit-trail.md @@ -0,0 +1,355 @@ +# F-60 — Observability + audit trail (4 responsabilidades grudadas) + +> **Tipo**: Feature +> **Epic pai**: [EP-06 Administração do sistema](../epics/EP-06-administracao-sistema.md) +> **Sprint de execução**: 1 (pré-busca, sem arquivo histórico formal) +> **Status**: ✅ Done (Sprint 1, pré-busca) — código em `backend/apps/audit/` (810 LOC + 806 LOC de testes) +> **Prioridade**: 🟠 Alta (entrega real) · 🔴 débitos S-10 (LGPD) e D-AUD-00 (refactor) abertos + +--- + +## Descrição (visão de produto) + +F-60 entrega, **em um único app Django**, a quarteta operacional que sustenta investigação de incidente, resposta a vazamento, monitoramento ativo e auditoria de moderação: + +1. **AuditLog** — tabela INSERT-only que registra toda escrita HTTP autenticada com autor, ação, recurso, status, IP e user-agent. +2. **RequestID + logs estruturados** — toda requisição recebe identificador único propagado em logs JSON e devolvido no header `X-Request-ID`. +3. **Sentry com PII scrubbing** — exceções vão para telemetria externa com remoção de senha/token/e-mail/cookie/CPF antes do envio; release tag por commit SHA; healthz droppado para não poluir quota. +4. **Security headers + healthcheck + AdminMetricsView** — Permissions-Policy + CSP (hoje Report-Only), endpoint `/healthz/` para UptimeRobot, e dashboard agregando KPIs para admin. + +**Anti-sycophancy**: este desenho está **inchado** — quatro responsabilidades em um único app é o maior débito estrutural do backend (DESIGN §0 e CONCERNS §D-02). Esta Feature **documenta o estado real do código entregue**, lista débitos abertos explicitamente (S-10, D-AUD-00..08), aponta gaps de cobertura (GAP-AUD-01..04) e mapeia o caminho de saída (F-61 refactor com ADR prévio em Sprint 9+, F-62 LGPD hotfix obrigatório em Sprint 5). + +Features futuras irmãs sob EP-06: + +- **F-61** — refactor em 4 apps (`apps.observability` + `apps.audit` puro + `apps.admin_bff` + `apps.security_headers`) +- **F-62** — AuditLog TTL + anonimização IP (LGPD blocker pré-go-live) +- **F-63** — Admin promote/demote role UI + +--- + +## Requisitos atendidos (rastreabilidade ↑) + +| ID | Requisito | Relação | +| -------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------- | +| [RF-006](../../requirements/RF/RF-006-audit.md) | Auditoria, observabilidade e telemetria operacional | Realiza diretamente (todas subseções) | +| [RNF-security](../../requirements/RNF/RNF-security.md) | Security headers, PII scrubbing, AuditLog INSERT-only | Realiza CA04, CA08, CA10 | +| [RNF-availability](../../requirements/RNF/RNF-availability.md) | `/healthz/` < 50ms + UptimeRobot < 1min | Realiza CA05, CA06 | +| [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md) | Retenção AuditLog (hoje **indefinida** — débito S-10) | Cumpre parcialmente; **F-62 fecha** | +| [RNF-perf](../../requirements/RNF/RNF-perf.md) | AdminMetrics tolera 1-2s; query budget ≤ 25 (sem guard hoje) | Cumpre parcialmente; gap GAP-AUD-02 | + +--- + +## Critérios de Aceitação (CAs) + +| ID | Critério | Como verificar | Status | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **CA01** | Toda requisição HTTP recebe `request_id` UUID único (16 hex chars = 64 bits), propagado em log lines e devolvido em response header `X-Request-ID`; cliente que enviou `X-Request-ID` é honrado | `tests/test_middleware.py` cobre stamp + reset + honor | ✅ (Sprint 1, pré-busca) | +| **CA02** | Logs em produção são JSON structured (formatter `json` em `base.py:286+`), cada linha carrega `request_id` + `user_id` via `RequestContextFilter`; dev usa formatter `console` legível | Smoke manual no journald + `LOGGING` dict em `base.py:307-321` | ✅ (Sprint 1, pré-busca) | +| **CA03** | Sentry SDK inicializa automaticamente em prod se `SENTRY_DSN` setado; no-op silencioso se vazio; release tag via `GIT_SHA[:12]`; `traces_sample_rate=0.1` e `profiles_sample_rate=0.05` env-overridable | `tests/test_sentry.py` (108 LOC) | ✅ (Sprint 1, pré-busca) | +| **CA04** | Security headers obrigatórios injetados em **toda** response: HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy (baseline em `security_headers_middleware.py:28-34`), CSP (Report-Only por default; flip via `CSP_ENFORCE=True` env) | `tests/test_security_headers.py` (145 LOC) | ✅ entrega real · 🟠 **débito S-03**: CSP Report-Only indefinido + `CSP_REPORT_URI=''` silencioso | +| **CA05** | Endpoint `GET /healthz/` (+ alias `/healthz` sem slash) retorna `{status, version, db, cache}` em ≤ 50ms p99 sem autenticação; checa DB via `SELECT 1` e cache via set/get round-trip | `tests/test_health.py` (4 testes, 43 LOC) — 200/alias/no-auth/version-env | ✅ entrega real · 🟢 gap **GAP-AUD-01**: sem assertion de latência | +| **CA06** | UptimeRobot externo bate `/healthz/` a cada 1min — degradação detectada em < 1min; nginx upstream check e smoke test do `deploy.sh` consomem o mesmo endpoint (rollback automático se 503 pós-restart) | Histórico UptimeRobot + `scripts/deploy.sh` | ✅ ativo em produção | +| **CA07** | AuditLog grava: `actor` (FK SET_NULL nullable), `action` (string `'{method} {path}'`), `request_path`, `request_method`, `response_status`, `ip_address` (IPv4/IPv6 cru — débito S-10), `user_agent` (truncado em 500 chars), `created_at` auto. Insert é post-response com `try/except Exception` (falha **nunca** quebra request) | `tests/test_middleware.py` + `apps/audit/middleware.py:57-85` | ✅ entrega real · 🔴 **débito S-10**: IP cru sem anonimização | +| **CA08** | AuditLog é **INSERT-only**: Django admin bloqueia `add`/`change` (`admin.py:12-16`); middleware só faz `objects.create`; nunca `update()` nem `delete()` pela aplicação. Garantia **convencional** (não enforced no DB — gap defense-in-depth) | Code inspection + `admin.py` readonly_fields cobre todos os campos | ✅ por design | +| **CA09** | Eventos auditados pela escrita HTTP: login, logout, login_failed, ban_created, ban_request_open, ban_request_decide, password_change, article_published, comentário criado/editado/removido via API. Captura é HTTP-method-driven (POST/PUT/PATCH/DELETE) com skip de `_SKIP_PATHS = {/api/v1/auth/refresh/, /admin/}` | Smoke test de cada rota canônica + `_WRITE_METHODS` em `middleware.py:20` | ✅ entrega real · 🟡 **débito D-AUD-07**: `/admin/` skipped — admin Django bypassa AuditLog custom | +| **CA10** | Endpoint `GET /api/v1/admin/metrics/` (gate `IsAuthenticated + IsAdminUser`) retorna agregados: `totals` lifetime, `period_stats` + `previous_period_stats` (delta), `per_article` ranking top 20, `time_series` em 5 séries, `category_breakdown` completo. Query param `?period=day | week | month | year`(default`week`) | `tests/test_admin_metrics.py` (426 LOC) | ✅ entrega real · 🟠 **débito D-AUD-02**: ~25 queries sem cache, sem `ScopedRateThrottle 'admin_metrics': 30/min`, sem `assertNumQueries` (regression guard) · gap GAP-AUD-02 | +| **CA11** | `get_client_ip` utility (`utils.py:12-30`) lê `HTTP_X_FORWARDED_FOR[0]` → fallback `REMOTE_ADDR` → `None`. **Não respeita `CF-Connecting-IP` explicitamente** — confia que nginx agrega Cloudflare em XFF. **Não respeita `NUM_PROXIES`** — pattern Django ausente | `utils.py:12-30` + cobertura indireta em `test_middleware.py` | ✅ entrega real · 🟡 **débito D-AUD-08**: frágil se Cloudflare bypassed | +| **CA12** | **(DESIGN §8 S-10 — LGPD blocker pré-go-live)** AuditLog hoje retém IP **cru** e **sem TTL**. LGPD Art. 16 exige tratamento por tempo necessário. **Mitigação obrigatória Sprint 5** via F-62: cron semanal anonimiza IP após 90d, purge completo após 2 anos, ADR formal de retenção por tabela. **Bloqueia go-live público regulatoriamente** | F-62 deliverable + ADR de retenção | 🔴 **PENDENTE — F-62 Sprint 5 obrigatória** | +| **CA13** | **(DESIGN §0 D-AUD-00 — refactor backlog Sprint 9+)** Módulo `apps.audit` carrega 4 responsabilidades grudadas (AuditLog + observability + AdminMetricsView + security_headers). Ordem de middleware em `base.py:55-79` é **crítica** — quebrar a ordem mata logs estruturados em silêncio. Refactor candidato para Sprint 9+ via F-61 com ADR prévio mandatório (ordem de extração: security_headers → observability → admin_bff → audit puro) | ADR de split + F-61 deliverable | ⏳ **PENDENTE — F-61 Sprint 9+** | +| **CA14** | Sentry `_before_send` faz scrub recursivo (depth ≤ 6) por chave em `_PII_KEYS`: `password*`, `*_token`, `csrf_token`, `authorization`, `cookie`, `email`, `cpf`, `phone`, `sendgrid_api_key`, `secret_key`, `jwt_signing_key`. Healthz droppado por path-suffix. `send_default_pii=False` como defesa em profundidade | `tests/test_sentry.py` | ✅ entrega real · 🟠 **débito D-AUD-01**: `?probe=1` em healthz **bypassa** drop e gera event Sentry (fix 2 linhas: `urlparse(url).path in {...}`) | +| **CA15** | RequestID e AuditLog middlewares usam defensive guard `request.user.is_authenticated` — divergem em `hasattr(request, 'user')` (RequestID tem, AuditLog não). Sem hasattr em AuditLog, ordem de middleware quebrada explode silenciosamente engolida pelo `try/except Exception` | `middleware.py:41-45` vs `:74` | ✅ funciona · 🟠 **débito D-AUD-03**: fix 5 linhas — extrair helper `_user_or_none(request)` | + +--- + +## User Stories + +### US60.1 — Dev investiga incidente via `request_id` em logs + +> **Como** desenvolvedor em incident response +> **Quero** correlacionar todas as linhas de log de uma requisição específica usando o identificador `X-Request-ID` que o cliente reportou +> **Para** reconstituir o que aconteceu em segundos, não em minutos, e identificar a causa raiz sem hipótese. + +- **Prioridade**: 🟠 Alta +- **Estimativa**: 3 Story Points +- **Sprint**: 1 (entrega real, pré-busca) +- **Status**: ✅ Done +- **CAs cobertos**: CA01, CA02 +- **Persona**: dev em incident response ([personas](../../requirements/personas-e-cenarios.md)) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Rastreabilidade end-to-end de requisição via request_id + Como dev em incident response + Quero correlacionar logs por request_id + Para reconstituir o incidente em segundos + +Cenário: Cliente envia X-Request-ID e backend honra + Dado que o cliente envia header "X-Request-ID: abc123def456" + Quando a requisição chega ao backend + Então toda linha de log do ciclo de vida do request carrega "request_id=abc123def456" + E a response devolve header "X-Request-ID: abc123def456" + E o dev consegue grep journalctl com esse mesmo valor + +Cenário: Cliente não envia X-Request-ID — backend gera + Dado que o cliente não envia header X-Request-ID + Quando a requisição chega ao backend + Então o RequestIDMiddleware gera "uuid.uuid4().hex[:16]" (16 hex chars) + E a response devolve esse identificador no header X-Request-ID + E o cliente pode logar em paralelo para correlação + +Cenário: ContextVar é resetado entre requisições + Dado uma requisição com request_id "aaa111" termina + Quando uma nova requisição entra com request_id "bbb222" + Então logs da segunda nunca contêm "aaa111" + E ContextVar.reset(token) foi chamado em finally do middleware + +Cenário: Investigação em prod via journalctl + Dado o cliente reporta erro com X-Request-ID "xyz789..." às 14:32 UTC + Quando o dev roda "sudo journalctl -u gunicorn --since '1 hour ago' | grep 'request_id=xyz789'" + Então todas as linhas daquele request aparecem agregadas + E o dev identifica o stack-trace + módulo afetado sem precisar abrir Sentry +``` + +--- + +### US60.2 — Admin revisa AuditLog de uma decisão de moderação + +> **Como** administrador investigando uma reclamação +> **Quero** consultar o AuditLog filtrado por autor, ação ou IP suspeito +> **Para** fundamentar uma decisão de moderação ou responder a um pedido LGPD com prova documental. + +- **Prioridade**: 🟠 Alta +- **Estimativa**: 2 Story Points +- **Sprint**: 1 (entrega real via Django admin) +- **Status**: ✅ Done (sem UI custom; via `/admin/audit/auditlog/`) +- **CAs cobertos**: CA07, CA08, CA09 +- **Persona**: admin investigador ([personas](../../requirements/personas-e-cenarios.md)) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Investigação de AuditLog por administrador + Como administrador + Quero consultar AuditLog filtrado + Para fundamentar moderação e responder LGPD + +Cenário: Admin lista últimas 50 ações de um usuário + Dado que admin está em "/admin/audit/auditlog/" + Quando filtra "actor__email=gabriel@interpop.com.br" + Então vê lista ordenada por created_at desc + E cada linha mostra (created_at, actor, action, response_status, ip_address) + E nenhum campo é editável (readonly_fields cobre todos) + +Cenário: Admin investiga burst de erro 5xx + Dado que o sistema teve pico de erros nas últimas 2h + Quando admin filtra response_status>=500 nas últimas 2h + Então vê agregação por action ordenada por contagem desc + E identifica qual rota explodiu + +Cenário: Admin tenta editar AuditLog (não pode) + Dado que admin abre uma linha em "/admin/audit/auditlog//" + Então todos os campos aparecem como readonly + E não há botão "Save" + E tentativa de POST direto retorna 403 (admin.py:12-16 bloqueia change/add) + +Cenário: Admin cumpre LGPD-DSAR + Dado que titular pede via DPO "que ações foram registradas sobre mim?" + Quando admin filtra actor= em "/admin/audit/auditlog/" + Então exporta CSV com todas as linhas + E entrega ao DPO para resposta formal ao titular + Mas (limitação atual): mudanças via Django admin sobre o titular NÃO aparecem nesse export (skip de "/admin/" — D-AUD-07); LogEntry nativo precisa ser anexado em paralelo +``` + +--- + +### US60.3 — UptimeRobot detecta downtime em < 1min via `/healthz/` + +> **Como** owner do sistema (Gabriel) +> **Quero** que UptimeRobot detecte degradação em < 1min +> **Para** ser notificado por SMS/e-mail antes que o leitor reporte e iniciar response. + +- **Prioridade**: 🔴 Imediato (disponibilidade percebida) +- **Estimativa**: 1 Story Point +- **Sprint**: 1 (entrega real) +- **Status**: ✅ Done +- **CAs cobertos**: CA05, CA06 +- **Persona**: owner em on-call ([personas](../../requirements/personas-e-cenarios.md)) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Monitoramento ativo de saúde via /healthz/ + Como owner em on-call + Quero ser notificado em < 1min de downtime + Para iniciar response antes que o leitor reporte + +Cenário: Tudo saudável + Dado que UptimeRobot bate "GET /healthz/" a cada 1min + Quando DB e cache respondem + Então response é 200 com {"status":"ok","version":"","db":"ok","cache":"ok"} + E UptimeRobot registra "up" + E nenhum alerta dispara + +Cenário: DB indisponível + Dado que Postgres parou de responder + Quando UptimeRobot bate "GET /healthz/" + Então _check_db lança exceção em "cursor.execute('SELECT 1')" + E response é 503 com {"status":"degraded","db":"error: ...","cache":"ok"} + E UptimeRobot dispara alerta SMS/e-mail em < 1min + +Cenário: Deploy quebrou — rollback automático + Dado que "scripts/deploy.sh" reiniciou o gunicorn + Quando smoke test bate "/healthz/" e recebe 503 + Então deploy.sh executa rollback para release anterior + E reinicia gunicorn no commit anterior + E confirma /healthz/ retornando 200 + +Cenário: Alias sem slash funciona + Dado que monitor configurou "GET /healthz" (sem trailing slash) + Quando bate o endpoint + Então response é 200 (alias montado em config/urls.py:21-22) + E não há redirect para "/healthz/" +``` + +--- + +### US60.4 — Admin abre dashboard `/api/v1/admin/metrics/` e vê KPIs do sistema + +> **Como** administrador +> **Quero** abrir o dashboard admin e ver totais + séries temporais + ranking de artigos + breakdown de editoria +> **Para** acompanhar saúde editorial sem precisar abrir Django admin e fazer queries manuais. + +- **Prioridade**: 🟠 Alta +- **Estimativa**: 5 Story Points +- **Sprint**: 1 (entrega real) +- **Status**: ✅ Done · 🟠 com débito D-AUD-02 (~25 queries sem cache/throttle/assertNumQueries) +- **CAs cobertos**: CA10 +- **Persona**: admin editorial ([personas](../../requirements/personas-e-cenarios.md)) + +#### Cenários BDD (Gherkin pt-BR) + +```gherkin +Funcionalidade: Dashboard admin com KPIs agregados + Como administrador + Quero KPIs do sistema em uma única view + Para acompanhar saúde editorial + +Cenário: Admin abre dashboard com period=week (default) + Dado que admin autenticado bate "GET /api/v1/admin/metrics/" + Então response é 200 com {totals, period_stats, previous_period_stats, per_article, time_series, category_breakdown} + E totals contém (users, subscribers, articles_published, view_count_total, comments_visible, likes) + E period_stats cobre últimos 7 dias e previous_period_stats cobre 7 dias antes (para delta) + E per_article é ranking top 20 (PER_ARTICLE_LIMIT=20) + E time_series tem 5 séries (comments, likes, subscribers, users, articles) bucketizadas por dia + +Cenário: Admin troca period para year + Dado admin bate "GET /api/v1/admin/metrics/?period=year" + Então time_series é bucketizado por mês (não por dia) + E period_stats cobre últimos 12 meses + +Cenário: Usuário não-admin é negado + Dado usuário autenticado mas role != admin/dev + Quando bate "GET /api/v1/admin/metrics/" + Então response é 403 (gate "IsAuthenticated + IsAdminUser") + E nenhuma query agregada é executada + +Cenário: Anônimo é negado + Dado cliente sem autenticação + Quando bate "GET /api/v1/admin/metrics/" + Então response é 401 (IsAuthenticated falha primeiro) +``` + +--- + +## Tasks (implementação — entregue Sprint 1, pré-busca) + +### Tasks US-bound — todas ✅ Done (Sprint 1, pre-busca) + +| ID | Descrição | Prioridade | Arquivo / referência | +| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------- | +| T60.1.1 | Bootstrap app `apps.audit` (`apps.py`, `__init__.py`, `models.py`, `admin.py`, `urls.py`) | 🔴 | ✅ Done (Sprint 1, pré-busca) — `backend/apps/audit/` | +| T60.1.2 | `RequestIDMiddleware` (gera/honra `X-Request-ID`, propaga via contextvars, stamp em response header) | 🔴 | ✅ Done (Sprint 1, pré-busca) — `middleware.py:24-54` | +| T60.1.3 | `RequestContextFilter` (`logging.Filter` que injeta `request_id` + `user_id` em todo LogRecord) | 🔴 | ✅ Done (Sprint 1, pré-busca) — `logging.py:30-41` | +| T60.1.4 | Config structlog/LOGGING em `base.py:286-360` (formatter `json` em prod, `console` em dev) | 🟠 | ✅ Done (Sprint 1, pré-busca) — `config/settings/base.py:286+` | +| T60.1.5 | `init_sentry(environment)` com gate `SENTRY_DSN`, `_scrub` recursivo por chave, drop healthz, release tag | 🟠 | ✅ Done (Sprint 1, pré-busca) — `sentry.py:56-90` | +| T60.2.1 | `AuditLog` model (BigAutoField, actor FK SET_NULL, action, request_path/method, response_status, ip_address, user_agent, metadata, created_at) | 🔴 | ✅ Done (Sprint 1, pré-busca) — `models.py:5-34` | +| T60.2.2 | Migrações iniciais `0001_initial.py` + `0002_initial.py` (schema base + índices `(actor, -created_at)`, `(action, -created_at)`, `(response_status, -created_at)`) | 🔴 | ✅ Done (Sprint 1, pré-busca) — `migrations/` | +| T60.2.3 | `AuditLogMiddleware` post-response com filtro `_WRITE_METHODS = {POST,PUT,PATCH,DELETE}` + skip `_SKIP_PATHS` + `try/except Exception` | 🔴 | ✅ Done (Sprint 1, pré-busca) — `middleware.py:57-85` | +| T60.2.4 | Django admin do AuditLog: `list_display`, `search_fields`, `readonly_fields` cobre tudo, bloqueia `add`/`change` | 🟠 | ✅ Done (Sprint 1, pré-busca) — `admin.py:5-16` | +| T60.3.1 | `SecurityHeadersMiddleware` injeta Permissions-Policy (baseline desabilita camera/mic/geo/payment/usb/sensors) + CSP (Report-Only por default, flip via `CSP_ENFORCE`) | 🟠 | ✅ Done (Sprint 1, pré-busca) — `security_headers_middleware.py:69-92` | +| T60.3.2 | `_build_csp(report_uri)` constrói policy baseline (`script-src 'self' 'unsafe-inline'` com comentário de compromisso consciente, `frame-ancestors 'none'`, `img-src 'self' data: https:`) | 🟠 | ✅ Done (Sprint 1, pré-busca) — `security_headers_middleware.py:37-66` | +| T60.4.1 | `healthz` view (FBV em `health_view.py:49-65`) — 2 checks (`_check_db` SELECT 1 + `_check_cache` set/get), retorna `version=GIT_SHA[:12]` | 🔴 | ✅ Done (Sprint 1, pré-busca) — `health_view.py:49-65` | +| T60.4.2 | URL `/healthz/` + alias `/healthz` em `config/urls.py:21-22` (sem auth, sem throttle) | 🔴 | ✅ Done (Sprint 1, pré-busca) — `config/urls.py:21-22` | +| T60.4.3 | `get_client_ip` utility (`utils.py:12-30`) — XFF[0] → REMOTE_ADDR → None | 🟠 | ✅ Done (Sprint 1, pré-busca) — `utils.py:12-30` | +| T60.5.1 | `AdminMetricsView` (`views.py:132-237`) com `IsAuthenticated + IsAdminUser`, query param `?period=`, agrega 6 totals + period × 2 + per_article + time_series × 5 + category_breakdown | 🟠 | ✅ Done (Sprint 1, pré-busca) — `views.py:132-237` | +| T60.5.2 | `_period_stats`, `_generate_buckets`, `_trunc_for` helpers em `views.py:42-129` | 🟠 | ✅ Done (Sprint 1, pré-busca) — `views.py:42-129` | +| T60.6.1 | Testes formais — `test_health.py` (43 LOC, 4 testes: 200/alias/no-auth/version-env) | 🔴 | ✅ Done (Sprint 1, pré-busca) — `tests/test_health.py` | +| T60.6.2 | Testes formais — `test_middleware.py` (84 LOC), `test_security_headers.py` (145 LOC), `test_sentry.py` (108 LOC), `test_admin_metrics.py` (426 LOC) | 🟠 | ✅ Done (Sprint 1, pré-busca) — `tests/` | +| T60.7.1 | Chamada `init_sentry(environment='production')` em `production.py` | 🟠 | ✅ Done (Sprint 1, pré-busca) — `config/settings/production.py` | + +### Tasks transversais (TX-NN) — herdadas, escalam para Sprint 5+ + +| ID | Descrição | Prioridade | Status | +| ----- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------------------------------------- | +| TX-60 | ADR formal de retenção AuditLog (90d anonim. IP + 2 anos purge total) — pré-requisito F-62 | 🔴 | ⏳ Sprint 5 (obrigatório pré-go-live) | +| TX-61 | ADR formal de split `apps.audit` em 4 apps (ordem: security_headers → observability → admin_bff → audit puro) | 🟠 | ⏳ Sprint 9+ (pré-requisito F-61) | +| TX-62 | Endpoint `POST /api/v1/security/csp-report/` que loga via structlog + `sentry_sdk.capture_message(level='warning')` | 🟠 | ⏳ Sprint 5 (destrava flip CSP enforce) | +| TX-63 | Hard-fail em `production.py` se `CSP_REPORT_URI == ''` e Sentry DSN configurado (auto-Sentry endpoint OU `raise ImproperlyConfigured`) | 🟠 | ⏳ Sprint 5 | +| TX-64 | Decisão "preencher `target_repr` + `metadata` vs. remover via migration" (D-AUD-04) | 🟡 | ⏳ Sprint 9+ (vinculado a F-61) | + +--- + +## Definition of Done — verificação + +- [x] CA01–CA11, CA14, CA15 verificados por automated test (`apps/audit/tests/` 5 arquivos, 806 LOC) +- [x] US60.1, US60.2, US60.3, US60.4 com cenários BDD cobertos por testes existentes ou smoke real em produção +- [x] Todas as Tasks 🔴 Imediate done (Sprint 1, pré-busca) +- [x] Code-review implícito (commits diretos em main pré-busca; sem PR formal histórico — documentação retroativa cobre esse gap) +- [x] Cobertura backend `apps/audit/` ≥ 85% local (806 LOC de teste para 810 LOC de código) +- [x] Documentação cruzada atualizada — RF-006 cita F-60, EP-06 lista F-60, DESIGN.md cita RF-006 +- [ ] **CA12 (S-10 LGPD)** verificável **apenas após F-62 Sprint 5** — 🔴 **bloqueia go-live público regulatoriamente** +- [ ] **CA13 (D-AUD-00 refactor)** verificável **apenas após F-61 Sprint 9+** — débito estrutural mapeado mas aceito + +**Status final**: ✅ **Done como entrega Sprint 1** com **2 CAs (CA12, CA13) marcados como débitos abertos** mapeados para Features futuras (F-62 obrigatória, F-61 opcional/estrutural). **Anti-sycophancy honesto**: o desenho está inchado, o spec não defende — apenas documenta para tornar o caminho de saída visível e seguro. + +--- + +## Open Questions (DESIGN §10 — escalar antes de refatorar) + +1. **(S-10 LGPD — hotfix obrigatório pré-go-live)** AuditLog TTL formal — 90 dias? 1 ano? 2 anos? Indefinido **não pode permanecer**. ADR explícito de retenção por tabela (audit_logs + comments soft-deleted + newsletter unsubscribed) precisa preceder o cron de F-62. +2. **(S-03)** CSP `Report-Only` indefinido — quando flip para `enforce`? Sem decisão, é cerimônia sem proteção real contra stored-XSS (combinado com S-01 sem sanitização HTML em comments/articles). +3. **(D-AUD-00 — refactor Sprint 9+)** Quando o split do app acontece? Ordem de extração: (a) `apps.security_headers` (independente, menor risco), (b) `apps.observability` (Sentry + RequestID + healthz — testar middleware order), (c) `apps.admin_bff` (mover AdminMetricsView), (d) `apps.audit` enxuto fica. +4. **(D-AUD-02)** `AdminMetricsView` N+1 sem cache — refactor com cache 60s + `ScopedRateThrottle 'admin_metrics': 30/min` + `assertNumQueries(<=25)` em test? Sem decisão, dashboard quebra em escala. +5. **(D-AUD-05)** `request_id` 16 hex chars (64 bits) — colisão teórica em 4.3 bilhões de requests (não-prático). Trocar para UUID completo (32 chars) por defesa em profundidade? Custo trivial; debate é UX de log. +6. **(O-07)** `X-Request-ID` no response é trade-off consciente — manter ou trocar por HMAC para zero-leak? Hoje aceitável (UUID aleatório, 64 bits — não há risco de enumeration). +7. **(D-AUD-07)** `/admin/` skip — manter e usar `LogEntry` nativo (formato divergente) OU remover skip e aceitar overhead? Decisão precisa documentar em ADR antes de F-62. +8. **(D-AUD-04)** `target_repr` + `metadata` — implementar de fato (action como enum + ContentType polimórfico + metadata estruturado) OU remover via migration? Decisão vinculada à seriedade que damos ao LGPD-DSAR. +9. **Log handler `console` vs `file` em prod** — hoje provavelmente journald via gunicorn → systemd. Confirmar com owner e documentar runbook. + +--- + +## Specs técnicas relacionadas + +- [DESIGN do módulo `audit`](../../specs/audit/DESIGN.md) — 526 LOC, fonte de verdade +- [CONCERNS §D-02, §S-03, §S-10, §D-08, §D-09, §O-07](../../specs/codebase/CONCERNS.md) +- [ARCHITECTURE — middleware order + signal flow](../../specs/codebase/ARCHITECTURE.md) +- [STRUCTURE — `backend/apps/audit/`](../../specs/codebase/STRUCTURE.md) +- [INTEGRATIONS — Sentry, UptimeRobot, Cloudflare](../../specs/codebase/INTEGRATIONS.md) + +--- + +## Cross-references resumidas + +| Direção | Onde | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ↑ Requisitos atendidos | [RF-006](../../requirements/RF/RF-006-audit.md), [RNF-security](../../requirements/RNF/RNF-security.md), [RNF-availability](../../requirements/RNF/RNF-availability.md), [RNF-lgpd](../../requirements/RNF/RNF-lgpd.md), [RNF-perf](../../requirements/RNF/RNF-perf.md) | +| ↑ Epic pai | [EP-06](../epics/EP-06-administracao-sistema.md) | +| → Sprint(s) | Sprint 1 (entrega histórica retroativa) · Sprint 5 (F-62 LGPD obrigatória) · Sprint 9+ (F-61 refactor) | +| → Specs técnicas | [DESIGN.md](../../specs/audit/DESIGN.md) + ADRs A27/A28/A29/A20 em `Improvement-system.md` | +| → Features filhas | n/a (F-60 é Feature, não Epic) | +| ← Features irmãs sob EP-06 | F-61 (refactor 4 apps — Sprint 9+), F-62 (LGPD TTL — Sprint 5 🔴), F-63 (promote/demote UI — Sprint 9+) | + +--- + +_F-60 ✅ Done como entrega histórica Sprint 1 (pré-busca). Documentação retroativa formalizada em 2026-06-09. **Anti-sycophancy**: este desenho carrega o maior débito estrutural do backend (DESIGN §0). Não defende — documenta. Próxima ação obrigatória: kickoff F-62 (LGPD AuditLog TTL) no início do Sprint 5. Skills aplicadas: `engenharia-de-requisitos`, `tlc-spec-driven`, `architecture-decision-records`, `security-requirement-extraction`._ diff --git a/docs/backlog/glossario.md b/docs/backlog/glossario.md new file mode 100644 index 00000000..5f60a521 --- /dev/null +++ b/docs/backlog/glossario.md @@ -0,0 +1,81 @@ +# Glossário de domínio — Interpop + +> Vocabulário canônico do produto. Toda US, CA, ADR e doc operacional **deve** usar estes termos consistentemente. + +--- + +## A + +- **Artigo** — Unidade editorial publicada por editor/admin. Tem título, excerpt, body (texto puro — [ADR-014](../planning/adrs/ADR-014-article-body-texto-puro.md)), autor, editoria, cover_url, status, published_at. +- **Audit log** — Registro imutável de eventos sensíveis (login, ban, publish, password change). Vive em `apps.audit`. +- **Autor** — Mesma pessoa que **Redator** ou **Editor**. Termo "autor" preferido em UX pública; "editor" usado no admin. + +## B + +- **Body** — Corpo do artigo (texto editorial longo). Texto puro hoje ([ADR-014](../planning/adrs/ADR-014-article-body-texto-puro.md)); JSONField estruturado em backlog. +- **BanRequest** — Pedido formal de ban aberto por editor; aprovado/rejeitado por admin. NUNCA aplicado direto pelo editor. + +## C + +- **Categoria / Editoria** — Sinônimos. Classificação editorial (Música, Moda, Cinema, Literatura, Cultura Digital). 5 fixas pré-cadastradas no MVP ([ADR-002](../planning/adrs/ADR-002-tags-pre-cadastradas.md)). +- **CA (Critério de Aceitação)** — Condição testável em booleano para aceitar uma Feature. `CA01..CANN` dentro do arquivo de Feature. +- **Comment** — Comentário em artigo. Suporta replies (parent_id), likes, soft-delete. +- **Cover** — Imagem de capa do artigo. URL armazenada em `Article.cover_url`. Servido pelo nginx local (curto prazo) ou Supabase Storage futuro ([Sprint 6](sprints/sprint-6-supabase-evaluation.md)). + +## D + +- **Done** — Status terminal de Epic/Feature/US/Task. Definido em [README §Definition of Done](README.md#definition-of-done-de-feature). +- **DPO (Data Protection Officer)** — Gabriel Marques · contato `privacidade.interpop@gmail.com` ([ADR-008](../planning/adrs/ADR-008-dpo-designado.md)). + +## E + +- **Editor** — Role que publica artigos. Pode abrir BanRequest. Hierarquia: `dev > admin > editor > user`. +- **Editoria** — Sinônimo de Categoria. Termo UX-facing preferido em pt-BR. +- **Epic (EP-NN)** — Macro-objetivo de produto. Decomposto em Features. IDs imutáveis. + +## F + +- **Feature (F-NN)** — Chunk de valor entregue dentro de um Epic. Contém Descrição + CAs + USs (com BDD) + Tasks. IDs imutáveis. + +## L + +- **Leitor anônimo** — Persona P-01. Sem cadastro. Maior fatia do MAU. +- **Leitor autenticado** — Persona P-02. Cadastrado, pode comentar/curtir/receber newsletter. + +## M + +- **MAU (Monthly Active Users)** — Métrica de uso. KVM 1 dimensionado para ≤30k MAU sustentado ([ADR-005](../planning/adrs/ADR-005-hostinger-kvm1.md)). +- **Migration** — Arquivo Django de DDL. Numeradas (`0001_initial.py`, `0002_search_indexes.py`...). + +## R + +- **Redator** — Sinônimo de Editor / Autor (UX-facing). +- **RF (Requisito Funcional)** — Capacidade do sistema. `RF-NNN`. Vive em `docs/requirements/RF/`. +- **RNF (Requisito Não-Funcional)** — Qualidade transversal (perf, security, a11y, LGPD, availability). `RNF-NN`. Vive em `docs/requirements/RNF/`. +- **Role** — Papel de autorização: `dev | admin | editor | user`. Hierarquia hard-coded. + +## S + +- **Search log** — Tabela operacional de buscas para detecção de abuso. Retention 7d, pseudonimizado (hash + IP truncado + bucket 5min — [RNF-lgpd](../requirements/RNF/RNF-lgpd.md)). +- **search_vector** — Coluna `tsvector` do Postgres que materializa o índice FTS. Mantida por trigger SQL ([ADR-018](../specs/busca-editorial/adrs/ADR-018-trigger-sql-fonte-verdade-consistencia.md)). +- **Sprint** — Janela de execução de ~1-2 semanas. Não confundir com "Sprint" como time-box rígido — aqui é mais "tema da janela". + +## T + +- **Task (TNN.M.K ou TX-NN)** — Unidade implementável de trabalho. Único nível onde termos técnicos são aceitos no título. Cada Task fechada cita o commit (SHA) que a entregou. + +## U + +- **US (User Story USNN.M)** — Necessidade de uma persona específica em formato canônico `Como [persona], quero [ação], para [valor]`. Inclui cenários BDD em Gherkin. + +## V + +- **Visão de produto** — Seção obrigatória em todo Epic/Feature. pt-BR sem jargão técnico. Conta o "porquê" e o "para quem". + +--- + +## Cross-references + +- Convenções de naming: [`backlog/README.md`](README.md) +- Skill canônica: [`engenharia-de-requisitos`](https://github.com/seekdevcore/sk-requirements-engineering-theskill) +- Architecture overview: [`docs/architecture/overview.md`](../architecture/overview.md) diff --git a/docs/backlog/sprints/sprint-4-busca-editorial.md b/docs/backlog/sprints/sprint-4-busca-editorial.md new file mode 100644 index 00000000..ebc9d8a0 --- /dev/null +++ b/docs/backlog/sprints/sprint-4-busca-editorial.md @@ -0,0 +1,112 @@ +# Sprint 4 — Busca editorial + +> **Período**: 2026-06-02 → 2026-06-09 (~7 dias de trabalho) +> **Tema**: Implementar busca editorial full-text como fundação de descoberta +> **Status**: ✅ Encerrado (PR #37 squash-merged em main como `2bdf73b` em 2026-06-09) + +--- + +## Objetivos + +1. Entregar US30.1 (busca por texto livre) end-to-end com qualidade de produção +2. Estabelecer fundação para Sprint 5 (filtros + deep-linking) sem retrabalho +3. Manter cobertura de testes ≥ 80% backend, ≥ 80% frontend `pages/Buscar/` +4. Zero regressão em features existentes +5. Honrar todos os ADRs aprovados na spec multi-agente + +--- + +## Escopo (Epics e Features) + +| Tipo | ID | Nome | Status | +| ------- | ---------------------------------------------- | --------------------- | --------------------------------------- | +| Epic | [EP-10](../epics/EP-10-busca-editorial.md) | Busca editorial | 🚧 (F-30 done; resto Sprint 5) | +| Feature | [F-30](../features/F-30-busca-texto-livre.md) | Busca por texto livre | ✅ Done | +| Feature | [F-31](../features/F-31-filtros-busca.md) | Filtros | ⏳ Sprint 5 (shell vazia entregue) | +| Feature | [F-32](../features/F-32-deep-linking-busca.md) | Deep-linking | ⏳ Sprint 5 (URL SSOT parcial entregue) | + +--- + +## User Stories executadas + +| ID | Título | Story Points | Status | +| ------ | ---------------------------------------- | ------------ | ------- | +| US30.1 | Leitor faz busca rápida pelo termo livre | 8 | ✅ Done | + +--- + +## Tasks 🔴 Imediato concluídas (com commit hash) + +| ID | Descrição | Commit | +| --------- | ------------------------------------------------ | -------------------- | +| T30.1.4b | Migration 0001 — CONFIGURATION pt_unaccent | `103e5ea` | +| T30.1.5b | Migration 0003 — triggers SQL | `df98846` | +| T30.1.5d | Migration 0005 — ENABLE ALWAYS triggers | `ffb88f6` | +| T30.1.X6 | useDebouncedValue 15 LoC | `ce18826` | +| T30.1.X7 | Bug 6 fix `?? undefined` | `2259605` | +| T30.1.X8 | `` (rejeita combobox) | `816e3fb` | +| T30.1.X12 | MSW handlers + worker DEV-only | `ffa5150`, `2bdf681` | +| T30.1.X13 | a11y.test.tsx vitest-axe + fix Skeleton landmark | `cbb9001` | +| TX-18 | Baseline Lighthouse coletada | `284997a` | +| T30.4.B1 | F2-B-01 — @transaction.atomic | `14649d7` | +| T30.4.B2 | F2-B-02 — Cache-Control private autenticado | `2362305` | +| T30.4.B3 | F2-B-03 — HMAC hard-fail em prod | `96cdad5` | + +_(Tabela completa em [F-30](../features/F-30-busca-texto-livre.md#tasks-implementação))._ + +--- + +## Métricas finais + +| Métrica | Resultado | +| -------------------------- | ------------------------------------------- | +| Tests backend | **325 passed** + 27 skipped (Postgres-only) | +| Tests frontend | **78 passed** em 10 files | +| Total | **403 passing, 0 regression** | +| Cov `apps.search` | ≥ 85% local | +| Cov `pages/Buscar` (Lines) | **84.15%** | +| Bundle Buscar lazy | 14.54 KB gz (gate ≤ +20 KB ✅) | +| ADRs materializadas | 35 (em `specs/busca-editorial/adrs/`) | +| CI checks no PR #37 | **15/15 pass** | +| Code reviews aplicados | 3 (Phases 1/2/3) — todos ≥ 🟠 corrigidos | + +--- + +## Reviews + +| Review | Veredito final | +| --------------------------------------------------------------- | ------------------------------------------------------------ | +| [REVIEW-PHASE-1](../../specs/busca-editorial/REVIEW-PHASE-1.md) | APROVADO COM RESSALVAS → fixes aplicados em commits Sprint 4 | +| [REVIEW-PHASE-2](../../specs/busca-editorial/REVIEW-PHASE-2.md) | APROVADO COM RESSALVAS → F2-B-01/02/03 fechados | +| [REVIEW-PHASE-3](../../specs/busca-editorial/REVIEW-PHASE-3.md) | APROVADO COM RESSALVAS → 2 BLOQUEIOs + 4 HIGHs fechados | + +--- + +## Lições aprendidas (continuar / evitar / experimentar) + +### Continuar + +- Spec multi-agente antes de implementar — 10 bugs reais detectados antes de uma linha de código +- 3 code reviews por fase + fix inline (não acumular dívida) +- BDD em Gherkin pt-BR como contrato de aceitação (não só "smoke manual") + +### Evitar + +- Confiar em commit message como contrato (BLOQUEIO-2 do REVIEW-PHASE-3: commit dizia `[a11y axe-core]` mas zero imports) +- Push sem rotação de secret crítica em prod (F2-B-03 quase foi para prod com `SEARCH_CURSOR_HMAC_SECRET == SECRET_KEY`) +- 60 commits acumulados antes do PR — exigiu squash que apagou granularidade + +### Experimentar (Sprint 5) + +- PRs menores por Feature (não por Epic completo) +- Branchar direto de `main` por Feature (não acumular em `develop`) +- Visual regression via Playwright `toHaveScreenshot` para 5 estados + +--- + +## Cross-references + +- [Epic EP-10](../epics/EP-10-busca-editorial.md) +- [Spec técnica DESIGN.md v3](../../specs/busca-editorial/DESIGN.md) +- PR mergeado: [seekdevcore/interpop#37](https://github.com/seekdevcore/interpop/pull/37) +- Próximo Sprint: [Sprint 5](sprint-5-filtros-deep-linking.md) diff --git a/docs/backlog/sprints/sprint-5-filtros-deep-linking.md b/docs/backlog/sprints/sprint-5-filtros-deep-linking.md new file mode 100644 index 00000000..5644eaeb --- /dev/null +++ b/docs/backlog/sprints/sprint-5-filtros-deep-linking.md @@ -0,0 +1,86 @@ +# Sprint 5 — Filtros + Deep-linking + dívidas técnicas da busca + +> **Período**: TBD (planejado pós-Sprint 4 — Junho 2026) +> **Tema**: Completar EP-10 com filtros funcionais + URL SSOT + endurecimento operacional +> **Status**: ⏳ Pending kickoff + +--- + +## Objetivos + +1. Entregar F-31 (filtros autor/editoria/datas) e F-32 (deep-linking + compartilhamento) +2. Fechar as 11 Tasks restantes do REVIEW-PHASE-3 marcadas como 🟡 Normal +3. Estabelecer Lighthouse CI gate automatizado (TX-16) — sai do "manual" +4. Materializar runbook de DR + scaling triggers +5. Endurecer pseudonimização LGPD do search_log (ADR-035) + +--- + +## Escopo (Features) + +| Tipo | ID | Nome | Status entrada | +| ------- | ---------------------------------------------- | -------------------------------- | -------------- | +| Feature | [F-31](../features/F-31-filtros-busca.md) | Filtros (autor, editoria, datas) | ⏳ Pending | +| Feature | [F-32](../features/F-32-deep-linking-busca.md) | URL deep-linking + share | ⏳ Pending | + +--- + +## Tasks restantes do REVIEW-PHASE-3 (alocadas aqui) + +| ID | Descrição | Prioridade | +| --------- | ---------------------------------------------------------------------------------------------- | ---------- | +| T30.1.X18 | Tests para `useSearchParamsState` (NaN guard, replace vs push) | 🟡 | +| T30.1.X19 | Test AbortSignal cancelando `fetchSearch` | 🟡 | +| T30.1.X20 | Visual regression Playwright `toHaveScreenshot` 5 estados | 🟡 | +| T30.1.X21 | E2E Playwright (input → results → load-more → article) | 🟡 | +| T30.1.X22 | Property-based (fast-check) `useDebouncedValue` + `canonicalKey` | 🟡 | +| T30.1.X23 | Avaliar custom 30-LoC highlighter vs mark.js 8 KB gz | ⚪ | +| T30.1.X24 | i18n extract strings pt-BR para `src/i18n/` | ⚪ | +| TX-13 | Runbook DR — `pg_dump --exclude-table-data` + reindex pós-restore | 🟡 | +| TX-14 | Doc scaling triggers — `>100GB OR p95>250ms` | ⚪ | +| TX-15 | Role Postgres `interpop_search_reader` (statement_timeout + work_mem + gin_fuzzy_search_limit) | 🟠 | +| TX-16 | Lighthouse CI gate em `/buscar?q=kpop` bloqueia PR | 🟠 | +| TX-17 | jest-axe + axe-playwright nos 5 estados E2E | 🟠 | +| TX-20 | NVDA + VoiceOver manual checklist | 🟡 | +| TX-22 | Investigar e mitigar CLS pré-existente (0.15+) | 🟠 | + +--- + +## Tasks novas previstas para F-31 + +T31.1-T31.7 — ver [F-31](../features/F-31-filtros-busca.md#tasks-previstas). + +## Tasks novas previstas para F-32 + +T32.1-T32.4 — ver [F-32](../features/F-32-deep-linking-busca.md#tasks-previstas). + +--- + +## Tasks de segurança alocadas (do SECURITY-REVIEW.md) + +| ID | Descrição | Prioridade | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| T30.4.X1 | Pseudonimização forte search_log (HMAC pepper + bucket 5min + IP/16) ([ADR-035](../../specs/busca-editorial/adrs/ADR-035-pseudonimizacao-forte-search-log.md)) | 🟠 | +| T30.4.X5 | Semgrep custom rules em CI (proibir `extra(where=)` + `dangerouslySetInnerHTML`) ([ADR-038](../../specs/busca-editorial/adrs/ADR-038-semgrep-custom-rules-ci-proibir-innerhtml-extra-where.md)) | 🟡 | +| T30.4.X7 | Test integration de não-bypass de trigger SQL ([ADR-039](../../specs/busca-editorial/adrs/ADR-039-test-integration-trigger-bypass-session-replication-role.md)) | 🟡 | + +--- + +## Definition of Done do Sprint + +- [ ] F-31 ✅ Done — todos os CAs 16-21 verificados +- [ ] F-32 ✅ Done — todos os CAs 22-27 verificados +- [ ] Lighthouse CI ativo em `.github/workflows/lhci.yml` bloqueando PRs +- [ ] Visual regression Playwright snapshot baseline criado +- [ ] Pseudonimização LGPD forte ativa em search_log +- [ ] Runbook DR escrito + smoke teste (`pg_dump --exclude-table-data` + reindex) +- [ ] Cobertura `pages/Buscar` ≥ 85%, `apps.search` ≥ 90% +- [ ] EP-10 inteiro pode ser movido para `done/` (F-30/F-31/F-32 todos done) + +--- + +## Cross-references + +- Sprint anterior: [Sprint 4](sprint-4-busca-editorial.md) +- Próximo Sprint: [Sprint 6 — Supabase evaluation](sprint-6-supabase-evaluation.md) +- Epic ativo: [EP-10](../epics/EP-10-busca-editorial.md) diff --git a/docs/backlog/sprints/sprint-6-supabase-evaluation.md b/docs/backlog/sprints/sprint-6-supabase-evaluation.md new file mode 100644 index 00000000..725f400c --- /dev/null +++ b/docs/backlog/sprints/sprint-6-supabase-evaluation.md @@ -0,0 +1,65 @@ +# Sprint 6 — Avaliação de Supabase (spike) + +> **Período**: TBD (planejado pós-Sprint 5) +> **Tema**: Spike técnico para decidir se Supabase entra como suplemento à stack atual +> **Status**: ⏳ Pending (gatilho: Sprint 5 fechado OU disco saturando OR demanda concreta de pgvector/realtime) +> **Tipo**: Sprint de descoberta (não entrega feature de produto) + +--- + +## Contexto + +Durante Sprint 4 (busca editorial), o tópico **"vamos usar Supabase + Cloudflare + Hostinger"** foi levantado. Anti-sycophancy ativa: Cloudflare e Hostinger já são lei do projeto (ADRs 003 + 005), então a única decisão real é **Supabase**. + +Análise feita em 2026-06-09 identificou 3 cenários com custos muito diferentes: + +| Cenário | Impacto | Recomendação | +| ------------------------------------------------------------------ | ------------------------------------------------- | ----------------------------- | +| (A) Supabase como **suplemento** (Storage/pgvector/Edge Functions) | Zero impacto no que existe | Avaliar Sprint 6 | +| (B) Supabase como **DB managed** (substitui Postgres self-hosted) | Quebra ADRs 018/019/021b da busca; exige Pro tier | Não justificável hoje | +| (C) Supabase como **replatform** (substitui Django) | Joga fora 60 commits + ADRs 001-014 | Inviável sem razão de produto | + +Decisão de 2026-06-09: **adiar para Sprint 6**, cenário A apenas, com gatilhos explícitos. + +📄 **ADR formal**: [ADR-015-supabase-evaluation-deferred](../../planning/adrs/ADR-015-supabase-evaluation-deferred.md) + +--- + +## Objetivos do Spike (timeboxed em 3 dias) + +1. **Spike Storage** — POC: subir uma capa de artigo para Supabase Storage, servir via CDN, comparar com média servida pelo nginx local. Decidir se vale migrar capas futuras (não retroativo). +2. **Spike pgvector** — Avaliar custo + viabilidade de embedding para semantic search (extensão do EP-10). Não implementar; apenas medir esforço. +3. **Spike Edge Functions** — Avaliar se algum endpoint atual seria mais bem servido em Edge (CORS-free, latência regional). Candidatos: OG meta tags crawler, sitemap, RSS. + +**Não-objetivos**: migrar Postgres existente, migrar Django auth, refazer admin. Cenários B e C estão fora do escopo deste Sprint. + +--- + +## Gatilhos para entrar neste Sprint + +Pelo menos **um** dos seguintes deve estar verdadeiro: + +- [ ] Sprint 5 fechado (EP-10 completo) +- [ ] Disco do KVM 1 ≥ 70% usado (capas de artigos + media saturando) +- [ ] Demanda concreta de produto por semantic search (ex.: "leitor pede 'artigos parecidos com este'") +- [ ] Demanda concreta por real-time (comments live, notificações push) + +--- + +## Entregáveis do Spike + +| Entregável | Formato | +| ------------------------------------------------ | -------------------------------------------------------------- | +| ADR-046 (Storage adoption decision) | `docs/planning/adrs/ADR-046-supabase-storage-{adopt,defer}.md` | +| Comparação de latência local vs Supabase Storage | `docs/performance/supabase-storage-benchmark.json` + README | +| Análise de custo mensal (3 cenários de uso) | Tabela no ADR-046 | +| Recomendação final + roadmap se adoptar | Atualizar este arquivo Sprint-6 | + +--- + +## Cross-references + +- Sprint anterior: [Sprint 5](sprint-5-filtros-deep-linking.md) +- ADR formal: [ADR-015 deferred](../../planning/adrs/ADR-015-supabase-evaluation-deferred.md) +- Stack atual: [Architecture overview §2](../../architecture/overview.md) +- ADR-005 Hostinger (premissa): [`docs/planning/adrs/ADR-005-hostinger-kvm1.md`](../../planning/adrs/ADR-005-hostinger-kvm1.md) diff --git a/docs/performance/README.md b/docs/performance/README.md new file mode 100644 index 00000000..936f6ca0 --- /dev/null +++ b/docs/performance/README.md @@ -0,0 +1,98 @@ +# Performance baselines — Interpop + +> **Fonte de verdade** dos baselines Lighthouse pré-implementação da busca editorial. Gate de regressão (ADR-031-FE) compara cada PR contra **`lighthouse-baseline-pre-busca-prod-desktop.json`** e **`lighthouse-baseline-pre-busca-prod-mobile.json`**. + +## TX-18 (🔴 Immediate) — baseline coletado 2026-06-04 + +Build production: `npm run build` (Vite 8) → `npx vite preview --port 4173`. Chromium via Playwright cache (`~/.cache/ms-playwright/chromium-1223`). Lighthouse 12 headless. + +### Tabela consolidada + +| Setup | Perf | A11y | B-P | SEO | LCP | FCP | CLS | TBT | SI | Total bytes | +| --------------------- | -----: | ---: | --: | --: | -------- | ----- | --------- | ----- | ----- | ----------- | +| **prod / desktop** ⭐ | **93** | 100 | 96 | 92 | **0.7s** | 0.6s | **0.153** | 0ms | 0.6s | 355 KiB | +| **prod / mobile** ⭐ | **81** | 100 | 96 | 92 | **3.1s** | 2.8s | **0.176** | 30ms | 2.8s | 355 KiB | +| dev / desktop | 67 | 100 | 96 | 92 | 3.1s | 1.9s | 0.153 | 0ms | 1.9s | 2862 KiB | +| dev / mobile | 43 | 100 | 96 | 92 | 18.1s | 10.5s | 0.176 | 240ms | 10.5s | 2862 KiB | + +(⭐) = baseline canônico para gate. Dev numbers ficam como referência diagnóstica; **não** são usados em CI. + +### NFR (DESIGN §0) — status hoje, sem busca + +| NFR | Alvo | Desktop | Mobile | Status | +| ----------------- | ----------- | --------------- | --------------- | --------------------------- | +| LCP ≤ 2.5s p75 | obrigatório | 0.7s | 3.1s | ⚠️ mobile **viola** já hoje | +| INP ≤ 200ms | obrigatório | (sem interação) | (sem interação) | TBT proxy: 0/30ms ✅ | +| CLS ≤ 0.1 | obrigatório | 0.153 | 0.176 | ❌ ambos **violam** já hoje | +| Bundle ≤ 500KB gz | obrigatório | 355 KiB | 355 KiB | ✅ folga 30% | + +### 🚨 Flags pré-existentes (não causados pela busca) + +Dois NFRs do DESIGN estão violados **antes** da busca ser implementada: + +1. **CLS 0.153–0.176**: layout shifts acima de 0.1. Investigar candidatos comuns — webfonts sem `font-display: optional`/`size-adjust`, imagens sem dimensões, lazy hero, ads/iframes injetados. Endereçar **antes** do PR final da US30 ou aceitar dívida explícita. +2. **LCP mobile 3.1s**: provável LCP element = imagem de cover do destaque. Aplicar `` no destaque, `fetchpriority="high"` no ``, ou converter para AVIF/WebP responsivo. + +A Fase 3 (frontend `/buscar`) **não pode piorar** essas métricas; gate Lighthouse CI (TX-16/ADR-031-FE) bloqueia PR se regredir. + +### Gate de regressão (TX-16 / ADR-031-FE) + +`.github/workflows/lhci.yml` deve assertar (sobre `/buscar?q=kpop`): + +- LCP delta ≤ +200ms vs `prod-mobile` baseline (3.1s + 200ms = 3.3s ceiling) +- INP ≤ 200ms (absoluto) +- CLS delta ≤ +0.02 vs baseline (0.176 + 0.02 = 0.196 ceiling) +- Bundle delta ≤ +20KB gz vs baseline (355 + 20 = 375 KiB ceiling) + +Falha → bloqueia merge. + +### Comando reprodução + +```bash +# 1. Build production +export PATH="$HOME/.nvm/versions/node/v22.22.3/bin:$PATH" # husky exige Node 20+ +npm run build + +# 2. Serve preview +npx vite preview --port 4173 & +PREVIEW_PID=$! +sleep 3 + +# 3. Chrome via Playwright (sem chrome system) +export CHROME_PATH="$HOME/.cache/ms-playwright/chromium-1223/chrome-linux64/chrome" + +# 4a. Desktop +npx -y lighthouse@12 http://localhost:4173/ \ + --preset=desktop \ + --output=json \ + --output-path=docs/performance/lighthouse-baseline-pre-busca-prod-desktop.json \ + --quiet \ + --chrome-flags="--headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage" + +# 4b. Mobile (sem preset) +npx -y lighthouse@12 http://localhost:4173/ \ + --output=json \ + --output-path=docs/performance/lighthouse-baseline-pre-busca-prod-mobile.json \ + --quiet \ + --chrome-flags="--headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage" + +# 5. Kill preview +kill $PREVIEW_PID +``` + +### Arquivos + +| Arquivo | Tamanho | Uso | +| ------------------------------------------------- | ------- | ----------------------- | +| `lighthouse-baseline-pre-busca-prod-desktop.json` | 463 KB | gate CI desktop | +| `lighthouse-baseline-pre-busca-prod-mobile.json` | 441 KB | gate CI mobile | +| `lighthouse-baseline-pre-busca-desktop.json` | 725 KB | dev server (referência) | +| `lighthouse-baseline-pre-busca-mobile.json` | 698 KB | dev server (referência) | + +### Open question + +- **CLS pré-existente**: deve ser corrigido **antes** ou **depois** da busca? Decisão pendente. Recomendação técnica: corrigir antes — caso contrário a busca herda regression latente e o gate CI passa por baixo limiar artificial. Task nova candidata: **TX-22** "Investigar e mitigar CLS 0.15+ pré-existente" (🟠 High). + +--- + +_Atualizado em 2026-06-04 — TX-18 concluída como parte de F5._ diff --git a/docs/performance/lighthouse-baseline-pre-busca-desktop.json b/docs/performance/lighthouse-baseline-pre-busca-desktop.json new file mode 100644 index 00000000..95a39260 --- /dev/null +++ b/docs/performance/lighthouse-baseline-pre-busca-desktop.json @@ -0,0 +1,17392 @@ +{ + "lighthouseVersion": "12.8.2", + "requestedUrl": "http://localhost:5173/", + "mainDocumentUrl": "http://localhost:5173/", + "finalDisplayedUrl": "http://localhost:5173/", + "finalUrl": "http://localhost:5173/", + "fetchTime": "2026-06-04T03:30:48.019Z", + "gatherMode": "navigation", + "runWarnings": [], + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "environment": { + "networkUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", + "hostUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "benchmarkIndex": 1402.5, + "credits": { + "axe-core": "4.12.0" + } + }, + "audits": { + "is-on-https": { + "id": "is-on-https", + "title": "Uses HTTPS", + "description": "All sites should be protected with HTTPS, even ones that don't handle sensitive data. This includes avoiding [mixed content](https://developers.google.com/web/fundamentals/security/prevent-mixed-content/what-is-mixed-content), where some resources are loaded over HTTP despite the initial request being served over HTTPS. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more about HTTPS](https://developer.chrome.com/docs/lighthouse/pwa/is-on-https/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Insecure URL" + }, + { + "key": "resolution", + "valueType": "text", + "label": "Request Resolution" + } + ], + "items": [] + } + }, + "redirects-http": { + "id": "redirects-http", + "title": "Redirects HTTP traffic to HTTPS", + "description": "Make sure that you redirect all HTTP traffic to HTTPS in order to enable secure web features for all your users. [Learn more](https://developer.chrome.com/docs/lighthouse/pwa/redirects-http/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "viewport": { + "id": "viewport", + "title": "Has a `` tag with `width` or `initial-scale`", + "description": "A `` not only optimizes your app for mobile screen sizes, but also prevents [a 300 millisecond delay to user input](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/). [Learn more about using the viewport meta tag](https://developer.chrome.com/docs/lighthouse/pwa/viewport/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "debugdata", + "viewportContent": "width=device-width, initial-scale=1.0" + }, + "guidanceLevel": 3 + }, + "first-contentful-paint": { + "id": "first-contentful-paint", + "title": "First Contentful Paint", + "description": "First Contentful Paint marks the time at which the first text or image is painted. [Learn more about the First Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint/).", + "score": 0.35, + "scoreDisplayMode": "numeric", + "numericValue": 1876.01325, + "numericUnit": "millisecond", + "displayValue": "1.9 s", + "scoringOptions": { + "p10": 934, + "median": 1600 + } + }, + "largest-contentful-paint": { + "id": "largest-contentful-paint", + "title": "Largest Contentful Paint", + "description": "Largest Contentful Paint marks the time at which the largest text or image is painted. [Learn more about the Largest Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 0.31, + "scoreDisplayMode": "numeric", + "numericValue": 3136.6887500000003, + "numericUnit": "millisecond", + "displayValue": "3.1 s", + "scoringOptions": { + "p10": 1200, + "median": 2400 + } + }, + "first-meaningful-paint": { + "id": "first-meaningful-paint", + "title": "First Meaningful Paint", + "description": "First Meaningful Paint measures when the primary content of a page is visible. [Learn more about the First Meaningful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "speed-index": { + "id": "speed-index", + "title": "Speed Index", + "description": "Speed Index shows how quickly the contents of a page are visibly populated. [Learn more about the Speed Index metric](https://developer.chrome.com/docs/lighthouse/performance/speed-index/).", + "score": 0.67, + "scoreDisplayMode": "numeric", + "numericValue": 1876.01325, + "numericUnit": "millisecond", + "displayValue": "1.9 s", + "scoringOptions": { + "p10": 1311, + "median": 2300 + } + }, + "screenshot-thumbnails": { + "id": "screenshot-thumbnails", + "title": "Screenshot Thumbnails", + "description": "This is what the load of your site looked like.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "filmstrip", + "scale": 3000, + "items": [ + { + "timing": 375, + "timestamp": 26977974325, + "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACMAPoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCoAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//Z" + }, + { + "timing": 750, + "timestamp": 26978349325, + "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACMAPoDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAQFAgMGCQEHCP/EAD4QAAIBAwMDAQYCCQEIAwEAAAECAwAEEQUSIQYTMUEUIlFSYaEycQcVIzZCdYGRsxYYJFZilLHS8TNDwcL/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIEBQP/xAAiEQEAAwACAgICAwAAAAAAAAAAAREhAjEEEgMiQVEUUmH/2gAMAwEAAhEDEQA/AP6ppSlApSlBgkqPJIi53RkBsggcjPn1pDNHMrNE4YKxQkehBwR/Qivzq+6e1m7utYmFvIZJrW8gt3kuFJV2KCFhzwAEL5/hLtgVstek76O4ZXtxHYub0vHbsiuzzy70fJ4yi5XPkFjjIrfrH7S36JmmRnGRn4V+K9XdAaxqWl9P2djYQ99bq6N009wRGI2WXtbyhyQN4GAPI8YrdH+j3qu31nSZDq4uYbS3ijecudxKIFYYPqSDz9ea16cf7Fv2JnVcbmAycDJ8n4VlX4l+ib9HutaLPpt11AixKyRM9qjtJiWNZMSSkkgPl1A25GF9K/baxz4xxmomyGiS5COVC5xWHtf/ACfeqnqLTpNRVI47mS1ZJe5vTOfwkY4Ix+Lz9K55eldRWOFTr87PG27eyNljhgc4ced3ke9x+IcY1HGJjUt3kMwlyMYIrbVfpETwwRxySNM6RqrSMOXIHk/U1X3nT88urT31vqc9v3fMajIHuoMjkc+5n68Z4BBxMaroKVzidPXaRCNNWmGEChirFgQuN34vP18/X0q00eyuLGCRLq9e8dn3B3GCBgceT8PvUE+lVmt6U2piApdSW7xMSGQZznGQR68ZH9c+lVo6e1ERBDr92SFxuI8tnz5+HGP61R0tK5p+m7tpGYa1cqPd2AAgJggnADYPj1z59at9IsprGBkuLuW7diDvkzwAAMYyfgT/AFqCdSoGsae9+luI5+w0UokDbNx4+AzjP1Oaqk6dvlMh/XdyC+4kqGHJXaPLHxx/+k8YDpKVqtImhtYYpJGldECtI3lyB5P51topSlKBSlKBSlKBSlKDV3T8v3p3T8v3rXSqjZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2GTPlPvXzuD5B/esKUGwS48L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oFKUoFQ9U1G20u2FxeuY4S4TdtJAJOBnHgfU1MrTdW0F3GI7qGKaMENtkUMMjwcH1oKVur9GWWONriQPJOtuuYXGWJIB8eCQRnxxWWldWaRqtxbw2Fw8sk5kCfs2A9wKWySOPxL/f86sTpWnltxsbUt3e/nsrnufP4/F9fNZJptjHLFKllbLLFntuIlBTIAODjjIAH9K19RB/1Npfs2oTmdxFY8zkxsMDcy5GRyMqwyPgaiJ1toUhmEd1I7RJG7KsDlsSIHXAxn8Jz/f4VeJY2iCYJbQKJm3y4jA3t8W+J+prCfTrG47vfs7aXu47m+JW34xjORzjA/sKYK9ep9Na9W0SSV53LBVWJju2orkjjxtdf71npnUem6lPBDaySmSaITIGiZQVIDDkjGdpBx5wam/qyxEyyiytu6rmRX7S7g54LA48nAyfpWcVlaxTLNFbQJMsYiV1jAYIPCg/D6eKYJFKUrIUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgV+TfpI/TfonRuryaVDaT6pqEOO8sThI4if4Sxz730Ar9Zrz367dpOt+oXdizNqFwST6/tGro8f4o+SZ9kmX75/tO2/wDwtL/1w/8ACn+07b/8LS/9cP8Awr+Z6Zrs/jfH+mbl6SUpSvLbKpur9Xk0TQZryCNJLgyRQQq/4e5JIsalsegLAn6Crmoesaba6xps9hfoXt5l2sASCOcggjkEEAg+hFBz+o3mpdPXukPdX51C1vrpLOZHhRDE7g7WTaB7u4YIbJwc54529U63Np2saXbwSlITHNd3m2Pe3YjAHHwJd0H5bvzEmLp4veWU+p6ld6j7E3ct0mWNVV8Fd52KNzAEgenOcZ5rDVOmItSvNUnububF/ZrYlVAHbiBYsFOPLbjn+nwrUV+UQ5uubE2E9xZWl7cSItuyRGLtmTvsVjwW+LAj7+Oa33/W2kWPtHeNwTbxSSyduIvgRkBxx6qWAJ8ZBGeDjGfo6CW4nl9vvIxLeRXexNgC9tNioPdyFAAPxBGRg18g6MtYrG9svbLlrS6nM7REIBhpe66khcsGOVy2TtOBWvoMLrrvS7V5VmttUVo4ZLg5snGY027mHHIAcHNTJOrtKWQqHmkTc8ayJEWV5FQuyL8WCg/TgjOQRVdNo8mvdR6y18bqDT+zFYiLt7RcRgl3wxGdrFtpx5C/UGoXUvSy2mjXCWDXs8krTRW8aIGFt7TIe9KMDJKq74J8DgeeVcR0Wg9UafrbSC1FxHst4rv9vEYwYpN21hnyPdYf0qF0t1Raa9ql/wCx363EQijlt4FQD9kcjfnPJLA8HGABxzk2em6Hb2ntbyO9xNdIsUjuAP2aghUAAACjLHHxY1h01obaFZraLqF1d20SCKBJ1jzEg4A3KoLYGBk5PFYmrw1Fi6w02SG3l7d2qTQXE6/stxxA22QYBJyD6DzUI/pD0YRySNDqaxxxR3DObKQAQvnbLnH4ODz9PFfIOho4LgOmr35ijW6SGFliKRrcHc4/BuODyMn0H1z9m6Hilsbm1bULgRz6ZFpTEIuRHGWww4/F7zfT6Uw1dazr1tpNzZ280V1NcXhcQpBCX3FV3EZ8A4B8n0qth640idrRLdb+WS5R3VEtJCU2SCOQPx7pVjznx/atXVdne3Gu9LG1e5T2e4leW4ihDhAYWUbsggAlgKkad0ja2F/bXMVxM4ihuInSQKe8Z5BJI7EAcllB4wByMUEPWestOOiXMsV3e2AktHu7a7W13l4lxmSNSCGA3KcEDg5xjmt9311otrd3Np33nuYBIpSMAl3jQuyDn8W0HzgZBGcjFVWv9FbOlprOzmur2aKwk03T45doECSBVySAM4AXJPOFPknm8t+lY4J9REV/dLY3xkeWzxGUDyDDlWK7hkknGcZ/tSf8GMfWFkbKK4ltb2N2tReyQiMO8MJ/jbaTxwcAZJwcDg10NvNHc28c8DrJDIodHU5DKRkEVy0HRawxBV1e/wC61kNPll2xAyQrnaMbMArubBHzHOeMdPZWsNlZwWtsgSCCNYo0H8KqMAf2FJoi26lKVFKUpQKUpQKUpQK89et/301/+YXH+Rq9Cq89Ot/301/+YXH+Rq7PD7lnkpTSlfM13svSalKV4j6FU3WN4un9NX109xd2yRJuaa0VWlQZGSoYEf39KuaruodJi1zRbvTbiaaGG5Ttu8O3cB64yCPtQVU3WNlBqUlm9pqGIr2Owln7Q7ccrhCmTnOG7ijIB5POKrNW6q361oEtm91FpD3lxFcXJEfYmWOCYt67+GjyDgA4PJ4qyn6Qhn9tL6jfZu72C/cjt8SQ7NoHueP2aZH0+taE6EsVEMJvr9rCC4kuIrJmjMSdxZFdAdm7aRK/BbjPGKqNdv8ApD0i5g7ltDdzEyQRqsYRy3eYrGchsDJGCCQRxkDNfZ/0haRbK8d1FdW99HK8L2U3bSVSqq5OS+wja6EYY53ADJ4qL1D01eW3Tdnp+n3uq33bvrR42Ywl7eKOVWJBKgNhV/i3E4HmrJ+jLX2pb6HUNQg1butK9+hj7sm5VQqwKFNu2NBjbxtB880w1rn680yMsyW2oSwItvJJOkHuRxzfgc5IOPjgEj4VG6h6lke/0qPSva1txqy2dxcqsfZcjcHjOTu4IxkDGRjNWGo9IQX51QzahfD9YxwxS7e3wIiSuMp5OTnOfPpWodE2q3G5NS1Fbf239YC1DRmNZslmIym7DEsSM45OMUyzaR4f0jaHNb3E8JuJYYkWUNEqvvjZwm4BWJHLDhgDg5xW2breJSscWkak917etg9swjR1ZozIrcvtKlRxz+eKzHRcC6FLoy6pqQ047VhhLRn2dFYMFQlMkcAe8WOBitl30fBc3d1dHUb+O4muortZI+3mKSNNg2goRgrwQc/0phrPTOstK1HXDpVs7m43SRhsqQXjOHXAYsCCDyQAcHBNZXvVlnbXTRrbXk8KXcdhJcRKpjSdyoCHLAnl1yQCBnGcg1v0Xp9NIu55LW/vTayyPN7G5QxLI53Ow93cMsScbsZJwBXP9S9Ly27+0aO+oSrc6ta309khjMQKzI0kg3DcOEzgNjPpzT8m0sl640xbaC5uYrq2t5IrqVpJVXEYt2KyBtrE5yOMZzW3T+pLXqDStUOmSzQT20ZBbMbMhK5VgQWQ/fkYIqEegNOkeRbi91Ce0b2kC1d07aLcEmRQQgbBJyMnI45q+07SntNOktLjUr2+DqUEtyU3quMYG1VB/Mgn41J6WO3JaP17Fa9N6fJrVpqPtZ0uO+LlI8XKhU7joQ2OC4JBwcHgVdX/AFlYWNzc280Fz34bpLQJ+zHddou4NpLAcqD5IOeMZrQvQmntpfsN1d311Eli2nQNKyBoIWC5ClVHPuJy2T7o+uTdEQSw38dzquo3JvipuWmELCTCbOV7e3xj08qCMc5s0kWnjqaB52SCx1CeKMsss0UO5YnCbyhGd27GBwpGTjzxW3Q+oLXWIr9oIp43sZTDPE4DMG2K/GwsDww4Bz6YzUCLoy0gj1CC2v8AUYLG+jKTWqSrs3GMRlwxUuGwAeGxnnFb9E6Wt9Jh1GKO8vJVv1US7yiYIjEe5dirtO1VHHHGQPNDUUdc6d2rhntb9HguYLV4jGrOGmwIyQGOASQMHkHyBX2Trayj0i5v5LHUU9llkiuIWjUPCUUM24ltnggj3ucjGTUa2/R/Z28Iji1PUgu+1cj9jybdt0fiPjwAcecfHJrbe9CWV5PcyzX+oZnuJLhwDHjMkYjZQNnjaoAP4hzg8mgrrjXZBrGpF9Sv109pdMNv2I4yU7zkbTlfwMdoJOSMnFXkHV1ncpePa2t5OltKYS0aqQzhyrD8XuYKnO/bxg+DUMdC24R1Oq6k242hJPZz/uzbo/8A6/iOfjWV30NY3VzPcyXt6LqR437o7WR23LKCNmHA3Y98MeBTCLbLXrWxvG01bKz1C5a/gNxEI4lOEEio247sDaWGfp4zXU1zWidIW2j31nc29/fSC0hmgiilMZXZK4dgSEBPKrjn09a6WgUpSopXnp1v++mv/wAwuP8AI1ehdeefXH76a/8AzC4/yNXZ4fcs8lLXylK7mXpPSlK8V9Coer6jDpdkbm4DMu9IkRANzu7BVUZ4yWYDnA55qZUDXNKtta02SxvRJ2nKuGjco6MrBlZWHghgCD9KCom6qlhvbWyfQNVF5cJK6RAwfhjdFY57mMe+p8+PrxVfofUxNxp9hbWeqXntb3rNLdSxdyEwzhHUgHBUFsDB8Aeau4enkj1Owv5NQv57izikhUyshDiQqW3e759xfGPH1OYtp0faWstpLDe34ntpriVJN6AnvuHkU4XBUsAfGR8cVcTULp/qaJtJtYrG21jVLjtmWRZWhM6IZXQFyWVTyrgBc8L/AH2XvXVjZzapFNZ3nc0+1kvHRe2WaONtrELvyPIIDYyDxW2w6KsrCW2ltb3UY5oVaMyLKqmWNnL7HwoBAZmIIAI3HBrRN0BpkiXSC61BEuIbi3ZVlUgRztukUZU+W5z558+MB8fruKK5lhuNF1WEQzQRTOwhKxrMwWNziQkqSccAkYOQK+y9f6ahvjHa3s8Volw5kiVCH7JxIB72R4bG7AO049Myrzo+1u2u2lv7/ddez9wgxj/4G3IR7nx5PxrI9IWYtNTtIry/is78SiS3WUbEMme4yAg43ZY48AkkAUmhBvOulgjugujX4uIGtdsUrRL3Y7iXtpIpDEYyDwcH6CpkPWNnLra6Z7NdCcTC3lYBWWKUpv2thicAEDcAVycZr5d9G2d207XF5fM0tvBb5DIpXsvvjYYX8QYk/A58YqXYdOQWOqS3tvd3ytOQ88Xd/ZzSBQvcYY4bAGcYBxyKYa06r1ZaabPfK9vcy2+nlBe3EYXZb7wCMgsCcKQx2g4BrLrC6v7S1tJrKK6ltVmzeizUNOItjcoD597bnHvYzistQ6WsL68uppWnWO82e1wI4Edzs4XeMZ8AA4IyAAcipWo6Ot5ei6S9vLWTtdl/Z3UB0znByDg8nkYPJ5pCqDSeqLW0sok9qu9XiaWP/fVVQEFxJ+wQ8gk7XTOB4wTjNQtK6i1LUtc0x/Zb5I7m5uQsSywiMW8RMe9hySd5Q8EeeMjg2N30zbaY0LaPp086iRHS2WZVhjlSIJHKwYgkAKg4JxgHaSM1P0fpi1079UyGWZ59PtBaKd+FccZYj1JIz/6GN3xRWdUarcDqyy0y1vdStoUs5Li59htBOxZmCxA5jfbnEpycfhrDQer5U0yzi1m2uJL4tJbyTRqgR540Z2T8XkBCCfwhgRmuksNIis9W1DURPPLPe7A4kK4RUztVcAEAbj5J81XydH6ZIt0khuGjnW4VU7nEPfJMpTjgsWPJzjOBgcVLiqVFi65sGtILmS1vIYHSKSV3VALcSRPL7/veioCcZxuX64wXrq2kNsINL1Kb2mf2eEoseHbtmTgl8eAQeeCDnGKkJ0TpfZv4rh7u5jvVZZRLL4LRiNmXAGGKgD6DIGASKsLTQoILiyuJbi6uZ7USbHncHJcAFiAAMgLgYAGCfjT6oga11haaOtqt3Z3hu5rY3TWqBDJGgxkH3sFsnGFJyQcZqTrvU1po7WCTRyyPehjCqlV3YAO0byMsc8KOTg4HFbtW0G31HULW/E91aXturRrNbSbS0bEEowIIIyAfGR6EU17QbbXLdbe8luBbbTHJEjDbKpIOGyDz7owwww5wRmsikuv0g6bCmoSR2l7PDZx3DmSNUIfsHEgHvZB4bG7AO049M3+naxFe6rf2CwyxyWaxOzNja4kBIxg5/hOc4qAvSVgsGpWomvP1ffiUS2fd/ZKZc9wqMZGSxOM4ycgCpGg9O2+jXVxcx3V7c3FxHHHLJcy7ywTO0+AAcMRxTDV1SlKilKUoFeefW/76a/8AzC4/yNXoZXnn1x++mv8A8wuP8jV2eH3LPJSUpSu5l6T0pSvFfQqr6l1ddC0afUZLaa5SHbujiKhsFgM+8QOM1aVz/X2n3OrdJahYWMLS3FwoRVVwh/ECTuJGOBQQ36x7M99a3mmTW15adtmSWeIIY5N+x9+7GCY2XHnOOOc1Hi67F1bLNp+i31yh06LVOJIlJifdwMtyw2nj71ZN0lptwY551u/bO4k/tDXDCYMqMoG4HwFdxgce8T5OapbboyOHqEwRxXsehppaWKFbsjcA7sUPvbsYYAHyMHn41Eu062stZnW20+11Ca1mKxG7ijYCNniEgJI8ABlBOeCR+dYaF1BJpv6M+ntUvo7q/lmt7RJGVlMjPLsTcSxGfeYZ5q4g6Y0+2vpbqy9ptGlUB4oJ2SJiFCBu3nbkKAPHoM5xQ9M2P+nLTRA1wLG17XaAk94CNgyDPrgqv9qYarX61WKxvprnTJ7eeyuTbTQzTxKFbtCUHduwdysuAMnJx8TU7UOp4rXp/TdWhsrm6hv3gSOOMorDvEBM7mA8sB59f61hc9HaXc3s11Mbszyzm5ZlnZPeMQiONuOCgC4+nx5qS/Tdi2i2GlhrhbSxeJ4R3SWBiIZMk5JwQP7UNVOndbG6vbW3n0S+thNczWRdpImCzxq7FOGJIIRsHx4rBOvrZ7e6kTT7iV7e4trdkhlifmdgqHdu28McEZ4PxHNWL9IabJt7jXLKLqW82mTgySKyP6eCrsMfWtEXQ2kQxduJ70A+z5zcs2ew26Lzn8JA/tz60w1jY9X+03lvaS6ZPbXMl9Jp8kckiHtyLAZgcqSCpQeR4J8VDt+uxK6TSaeYtPXTpNQnmaYFo1RyrAKBz+HPn1q1uekNOuZJpZJLxZZLz27uR3DRssvb7eVK4IBT3cfCsIeitFhSBEiuO1FDJb7GuHZZIpG3Mjgn3lyTwaCGOuALdZG0TUwz3EMCKUVAxlztwzlQcEYIGcEj05rbZ9Y+2IIbfSro6ruuFaxaSMMvZKhzvztxl0xzzuHjnExOlLFLK3tfaNQeK3mjni7t08hVkOUGWJ4H39c1Va/0ep7E+jm5XUPbjcNOJyhVXI7q5DKdrBVGOccEDIphqLJ1R+rerLg6peXcNn7NLO1o/ZYQ9uNXJ9wlwNu7yTkg4wMZ6PSuoBeamthc2c1ncyWwvIldlYPHkA8g8MCVyPqME1GforRnvHuJIrhy8kkjRvcOyMZE2yZUnBDDyKsNJ0Gy0yYTQd6SYRC3WSeVpGSMHIQEnx9zxknAoa5e+6il1DW+mLvT4rtdNlvLmPeswAuQkE3BTPILICpPw9M1e9OdRW/UX6whijaJ7VlR9squCHQMMMhIyM4ODwR6+aj/AOhdE91dl32Elkmjt/apBFG0iur7VzgAiRuPHPGKnaJ03Y6K872LXQeaNInaSdpMhBhT7xPIHH9KDkelOtTp3TVgNdtr/aunS3gvZJFlM4iI3jG4tu99cZ81dw9aJLLHbtpl5BdyuyRrOvbRwsZkJDkYPAIxjyD6c1JXozSRbWltIs81tbW0tokUkmVMUuN6n45wOfTFVPUXSMsqaXb2smo3Njbu8rk3YknD7dqY72UKgF855ztI9aDLS/0gw6gLTt6RqBaZIJJBGnc7SzH3Ccf8uGbxgH18VlD11LcTQxW/T2ou1w9zFDmWAbpIHKup9/j8J5+lTdD6ZeP2e/1a5uG1gJsnlt5jGsyKzGMSKm1WKqQM7Rnn04qTbdKWNtJavDLdB7aWeaMmTOHmJMh8c5LE/TNJoi1cnXdpNpjX9rY3U1vBYRaldYKhoYpAWAxn3mAViQPQeeQKzbrZBfSRLpV6bWK/isJLrfHtVpVjMbY3bip7qemRmpEPROjw2iWyJcCAWy2ciiZgJoVztR8fiA3EfHBIzg1vm6VsJTdEvcr7TdxXzhZMDux7QhHHAHbTj/lH1yw1RDrC00TQbi9lgv5IUurvue1XMZdTHKQ6rlve9dqj+FfI4z3gOQD8a5W46E0a5ikjnF26yC4V8XDLuWdg0inbjgsAfp+XFdPBGIYUjVmYIoUFzknHxPqaDOvPPrj99Nf/AJhcf5Gr0Mrzz64/fTX/AOYXH+Rq6/E7lOSkr5mlK7WXpRSlK8Z9CqzqTVhoukvetF3VWSOM+9tVd7qu5j6Ku7JPoAas6o+stJn1rQ2srYx5aaJ3V5Gj3qrhiA65KHjhgCR96DkLrqOa61fSNUvbdbays729jV7e5aVbiOO3lLNjaoI90Y885/rj1f1JdXejW1vPZC1nultr+0aG6YgqLmFWRyFG04lXxuByfhz0OidKduTvaxLPcmGcy2cT3ss4t1MYRl3tgvn3vxA4DEeKnDpHQ/ZJLU2CtA6rHseR22orBlRcn3VBAIVcDgcVUU9x1ne2s2oWd3pdumo2kiAIt2zxyo8bOpUiPeW9xgVCEjz45r70jqI1Xqq71CMSxw3ujafdiJ3LBC7T+B4BwFBx5xV0/SujSRoslmXZJe/3GlcyF9uzLOTub3fdwSRjjxWmDo3QoABHZvtCQxhWuJWULE2+MAFsAK3I/M/E0wdDSlKilKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFeeXXH769QfzC4/yNXobXnl1x++nUH8wuP8AI1dfidyzyUlKUrtZelFKUrx30K+MyqAWYAE4GT619qDrFpHfWiwzbgvcVsqcEEHIIPxB5oJqsGUMpBUjII9a+1xEGmRxQrGJ7g42LuL847ZP5fTPwqTb2arZahmWZt8ER5bxx4AHpx/3q0luuPAyaxDqSAGBJ54NchJpEMsV4ss1w+e4CWcc7tueMY8HH5cVhLpyNdl+9MGyvIYcbsD4enkfA81fVLdpTxXL3LPcpHevIyytpzMVQ4XJUk/X/wBCotlp0c9xbmWSVlMDEpkbfeY8Yx4Hp8KyrsQylSwYED1Br6CCAQcg1ysNhEvT1yAz4dwxyR4yPd8eOfFLWzWOykAllIF4DgkYP8PjH1z+YBq0W6ncu7bkbsZxnmvtcUtgiO1z3ZmmjKqrMQSAgOPT12jPxya3xWS2qK0c0xZbWU5Zhkn3uScc+n9hQt11K4Ke32WsLiWUl0UkEjAy/jGPHHir24s1F5ft3ZiWtR5f6Y//AJ+5+NKLX4IYZBBHxFfN679m5d+M7c84rj7GxSO7hKyy8SwYGRgYUnxjHqRWcmlxSahcyNLMG3SjhgPQkHx5GePyFKLdasiOpZWUqMgkHjjzWQIIyDkGuOtdLiYO7TTlw4wdw4wW8ccZPJr5NZLgKZZiEaf+LBbKepAz64/oKUW7FiFBLEADkk1iZYwcF1B44z8fH/eubvNNjmhhZ5Zt628SBgwB4Pnx5NQjpse9j3ZubnB5HOMcnjzx5pWluzVgwypB/KvtcUdPjjsHhSSUIbdF8jIAYDjjiplupFrqdtvcxqTMMnJzuOQT6g7ec/E0ot1NK0WEfasbePcz7I1XcxyTgeT9a31FKUpQK88uuP306g/mFx/kavQ2vPLrj99OoP5hcf5Grr8TuWeSkpSvldqP/9k=" + }, + { + "timing": 1125, + "timestamp": 26978724325, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1500, + "timestamp": 26979099325, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1875, + "timestamp": 26979474325, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2250, + "timestamp": 26979849325, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2625, + "timestamp": 26980224325, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 3000, + "timestamp": 26980599325, + "data": "data:image/jpeg;base64,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" + } + ] + } + }, + "final-screenshot": { + "id": "final-screenshot", + "title": "Final Screenshot", + "description": "The last screenshot captured of the pageload.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "screenshot", + "timing": 1317, + "timestamp": 26978915852, + "data": "data:image/jpeg;base64,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" + } + }, + "total-blocking-time": { + "id": "total-blocking-time", + "title": "Total Blocking Time", + "description": "Sum of all time periods between FCP and Time to Interactive, when task length exceeded 50ms, expressed in milliseconds. [Learn more about the Total Blocking Time metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/).", + "score": 1, + "scoreDisplayMode": "numeric", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "scoringOptions": { + "p10": 150, + "median": 350 + } + }, + "max-potential-fid": { + "id": "max-potential-fid", + "title": "Max Potential First Input Delay", + "description": "The maximum potential First Input Delay that your users could experience is the duration of the longest task. [Learn more about the Maximum Potential First Input Delay metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-max-potential-fid/).", + "score": 1, + "scoreDisplayMode": "numeric", + "numericValue": 42, + "numericUnit": "millisecond", + "displayValue": "40 ms" + }, + "cumulative-layout-shift": { + "id": "cumulative-layout-shift", + "title": "Cumulative Layout Shift", + "description": "Cumulative Layout Shift measures the movement of visible elements within the viewport. [Learn more about the Cumulative Layout Shift metric](https://web.dev/articles/cls).", + "score": 0.75, + "scoreDisplayMode": "numeric", + "numericValue": 0.15287, + "numericUnit": "unitless", + "displayValue": "0.153", + "scoringOptions": { + "p10": 0.1, + "median": 0.25 + }, + "details": { + "type": "debugdata", + "items": [ + { + "cumulativeLayoutShiftMainFrame": 0.15287, + "newEngineResult": { + "cumulativeLayoutShift": 0.15287, + "cumulativeLayoutShiftMainFrame": 0.15287 + }, + "newEngineResultDiffered": false + } + ] + } + }, + "errors-in-console": { + "id": "errors-in-console", + "title": "Browser errors were logged to the console", + "description": "Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns. [Learn more about this errors in console diagnostic audit](https://developer.chrome.com/docs/lighthouse/best-practices/errors-in-console/)", + "score": 0, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "sourceLocation", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "description", + "valueType": "code", + "label": "Description" + } + ], + "items": [ + { + "source": "network", + "description": "Failed to load resource: net::ERR_ADDRESS_UNREACHABLE", + "sourceLocation": { + "type": "source-location", + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/auth/me/", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/auth/me/", + "urlProvider": "network", + "line": 0, + "column": 0 + } + } + ] + } + }, + "server-response-time": { + "id": "server-response-time", + "title": "Initial server response time was short", + "description": "Keep the server response time for the main document short because all other requests depend on it. [Learn more about the Time to First Byte metric](https://developer.chrome.com/docs/lighthouse/performance/time-to-first-byte/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 9.386, + "numericUnit": "millisecond", + "displayValue": "Root document took 10 ms", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "responseTime", + "valueType": "timespanMs", + "label": "Time Spent" + } + ], + "items": [ + { + "url": "http://localhost:5173/", + "responseTime": 9.386 + } + ], + "overallSavingsMs": 0 + }, + "guidanceLevel": 1 + }, + "interactive": { + "id": "interactive", + "title": "Time to Interactive", + "description": "Time to Interactive is the amount of time it takes for the page to become fully interactive. [Learn more about the Time to Interactive metric](https://developer.chrome.com/docs/lighthouse/performance/interactive/).", + "score": 0.77, + "scoreDisplayMode": "numeric", + "numericValue": 3136.6887500000003, + "numericUnit": "millisecond", + "displayValue": "3.1 s" + }, + "user-timings": { + "id": "user-timings", + "title": "User Timing marks and measures", + "description": "Consider instrumenting your app with the User Timing API to measure your app's real-world performance during key user experiences. [Learn more about User Timing marks](https://developer.chrome.com/docs/lighthouse/performance/user-timings/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "15 user timings", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "timingType", + "valueType": "text", + "label": "Type" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 0.01, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 0.01, + "label": "Duration" + } + ], + "items": [ + { + "name": "Update Blocked", + "startTime": 359, + "duration": 11.399, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 373.399, + "duration": 58, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 435.5, + "duration": 1.199, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 438.399, + "duration": 0.901, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 599.5, + "duration": 13.399, + "timingType": "Measure" + }, + { + "name": "Update Blocked", + "startTime": 643.8, + "duration": 5.099, + "timingType": "Measure" + }, + { + "name": "​PageLayout", + "startTime": 654.599, + "duration": 22.201, + "timingType": "Measure" + }, + { + "name": "​Link", + "startTime": 658.399, + "duration": 0.7, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 659.199, + "duration": 0.801, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 660, + "duration": 0.599, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 660.599, + "duration": 0.701, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 661.3, + "duration": 0.7, + "timingType": "Measure" + }, + { + "name": "​Button", + "startTime": 662.099, + "duration": 0.201, + "timingType": "Measure" + }, + { + "name": "​Button", + "startTime": 662.3, + "duration": 0.2, + "timingType": "Measure" + }, + { + "name": "​Link", + "startTime": 675.3, + "duration": 0.299, + "timingType": "Measure" + } + ] + }, + "guidanceLevel": 2 + }, + "critical-request-chains": { + "id": "critical-request-chains", + "title": "Avoid chaining critical requests", + "description": "The Critical Request Chains below show you what resources are loaded with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load. [Learn how to avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "54 chains found", + "details": { + "type": "criticalrequestchain", + "chains": { + "96CA62C53748FA62EC5B629E5F0ECB6F": { + "request": { + "url": "http://localhost:5173/", + "startTime": 26977.603796, + "endTime": 26977.614672, + "responseReceivedTime": 26977.614207, + "transferSize": 2584 + }, + "children": { + "180065.8": { + "request": { + "url": "http://localhost:5173/@react-refresh", + "startTime": 26977.63695, + "endTime": 26977.663143, + "responseReceivedTime": 26977.662179, + "transferSize": 112187 + } + }, + "180065.15": { + "request": { + "url": "http://localhost:5173/site.webmanifest", + "startTime": 26977.672285, + "endTime": 26977.715374, + "responseReceivedTime": 26977.714855000002, + "transferSize": 796 + } + }, + "180065.2": { + "request": { + "url": "http://localhost:5173/@vite/client", + "startTime": 26977.629606, + "endTime": 26977.705533, + "responseReceivedTime": 26977.704497, + "transferSize": 210114 + }, + "children": { + "180065.21": { + "request": { + "url": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "startTime": 26977.711158, + "endTime": 26977.719203, + "responseReceivedTime": 26977.718636, + "transferSize": 3768 + } + } + } + }, + "180065.3": { + "request": { + "url": "http://localhost:5173/src/main.tsx", + "startTime": 26977.630027, + "endTime": 26977.633902, + "responseReceivedTime": 26977.632413999996, + "transferSize": 2330 + }, + "children": { + "180065.9": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "startTime": 26977.64129, + "endTime": 26977.671778, + "responseReceivedTime": 26977.670138999998, + "transferSize": 38613 + }, + "children": { + "180065.17": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "startTime": 26977.677468, + "endTime": 26977.685644999998, + "responseReceivedTime": 26977.682009000004, + "transferSize": 7922 + } + } + } + }, + "180065.10": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "startTime": 26977.641872, + "endTime": 26977.686801, + "responseReceivedTime": 26977.679790000002, + "transferSize": 821294 + }, + "children": { + "180065.19": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "startTime": 26977.707561, + "endTime": 26977.711616, + "responseReceivedTime": 26977.710416, + "transferSize": 14940 + } + } + } + }, + "180065.11": { + "request": { + "url": "http://localhost:5173/src/styles/global.css", + "startTime": 26977.64246, + "endTime": 26977.686632, + "responseReceivedTime": 26977.685102000003, + "transferSize": 16873 + } + }, + "180065.12": { + "request": { + "url": "http://localhost:5173/src/App.tsx", + "startTime": 26977.642949, + "endTime": 26977.706376, + "responseReceivedTime": 26977.705832000003, + "transferSize": 2529 + }, + "children": { + "180065.22": { + "request": { + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "startTime": 26977.712812, + "endTime": 26977.71668, + "responseReceivedTime": 26977.716110000005, + "transferSize": 21611 + }, + "children": { + "180065.23": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "startTime": 26977.720474, + "endTime": 26977.733832, + "responseReceivedTime": 26977.727798000004, + "transferSize": 399267 + } + }, + "180065.24": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "startTime": 26977.720759, + "endTime": 26977.731148, + "responseReceivedTime": 26977.72872, + "transferSize": 3458 + } + }, + "180065.25": { + "request": { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "startTime": 26977.721016, + "endTime": 26977.732636, + "responseReceivedTime": 26977.729314, + "transferSize": 9815 + }, + "children": { + "180065.48": { + "request": { + "url": "http://localhost:5173/src/components/ErrorFallback.css", + "startTime": 26977.771935, + "endTime": 26977.791191, + "responseReceivedTime": 26977.790070000003, + "transferSize": 3123 + } + } + } + }, + "180065.26": { + "request": { + "url": "http://localhost:5173/src/pages/Home.tsx", + "startTime": 26977.721265, + "endTime": 26977.73286, + "responseReceivedTime": 26977.729824000002, + "transferSize": 22299 + }, + "children": { + "180065.53": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "startTime": 26977.778126, + "endTime": 26977.798714, + "responseReceivedTime": 26977.797765, + "transferSize": 35878 + }, + "children": { + "180065.81": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "startTime": 26977.805475, + "endTime": 26977.834408, + "responseReceivedTime": 26977.833106000002, + "transferSize": 3997 + } + } + } + }, + "180065.54": { + "request": { + "url": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "startTime": 26977.778374, + "endTime": 26977.799702, + "responseReceivedTime": 26977.799088000003, + "transferSize": 689 + } + }, + "180065.55": { + "request": { + "url": "http://localhost:5173/src/pages/Home.css", + "startTime": 26977.778626, + "endTime": 26977.803656, + "responseReceivedTime": 26977.803042, + "transferSize": 9483 + } + } + } + }, + "180065.27": { + "request": { + "url": "http://localhost:5173/src/pages/News.tsx", + "startTime": 26977.721485, + "endTime": 26977.733213, + "responseReceivedTime": 26977.730504, + "transferSize": 31303 + }, + "children": { + "180065.49": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "startTime": 26977.772218, + "endTime": 26977.793797, + "responseReceivedTime": 26977.793217, + "transferSize": 12632 + }, + "children": { + "180065.80": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCard.css", + "startTime": 26977.802311, + "endTime": 26977.83472, + "responseReceivedTime": 26977.832658, + "transferSize": 6902 + } + } + } + }, + "180065.50": { + "request": { + "url": "http://localhost:5173/src/utils/categoryVariant.ts", + "startTime": 26977.772462, + "endTime": 26977.794579, + "responseReceivedTime": 26977.794052999998, + "transferSize": 2562 + } + }, + "180065.51": { + "request": { + "url": "http://localhost:5173/src/services/articleService.ts", + "startTime": 26977.777504, + "endTime": 26977.798375, + "responseReceivedTime": 26977.794842, + "transferSize": 9838 + } + }, + "180065.52": { + "request": { + "url": "http://localhost:5173/src/pages/News.css", + "startTime": 26977.777836, + "endTime": 26977.797365, + "responseReceivedTime": 26977.796618000004, + "transferSize": 1882 + } + } + } + }, + "180065.28": { + "request": { + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "startTime": 26977.721692, + "endTime": 26977.733404, + "responseReceivedTime": 26977.731419, + "transferSize": 19118 + }, + "children": { + "180065.43": { + "request": { + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "startTime": 26977.769647, + "endTime": 26977.79077, + "responseReceivedTime": 26977.778822, + "transferSize": 4791 + }, + "children": { + "180065.77": { + "request": { + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "startTime": 26977.795496, + "endTime": 26977.830359, + "responseReceivedTime": 26977.828386, + "transferSize": 21741 + }, + "children": { + "180065.92": { + "request": { + "url": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "startTime": 26977.838564, + "endTime": 26977.84811, + "responseReceivedTime": 26977.846067000002, + "transferSize": 20091 + } + }, + "180065.93": { + "request": { + "url": "http://localhost:5173/src/components/layout/Navbar.css", + "startTime": 26977.838812, + "endTime": 26977.850973, + "responseReceivedTime": 26977.849832000004, + "transferSize": 8587 + } + } + } + }, + "180065.78": { + "request": { + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "startTime": 26977.795791, + "endTime": 26977.830492, + "responseReceivedTime": 26977.829097, + "transferSize": 21715 + }, + "children": { + "180065.94": { + "request": { + "url": "http://localhost:5173/src/components/layout/Footer.css", + "startTime": 26977.840183, + "endTime": 26977.851218, + "responseReceivedTime": 26977.850399, + "transferSize": 5121 + } + } + } + }, + "180065.79": { + "request": { + "url": "http://localhost:5173/src/components/layout/PageLayout.css", + "startTime": 26977.796392, + "endTime": 26977.834062, + "responseReceivedTime": 26977.83218, + "transferSize": 1789 + } + } + } + }, + "180065.44": { + "request": { + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "startTime": 26977.770819, + "endTime": 26977.787942, + "responseReceivedTime": 26977.779359, + "transferSize": 3750 + }, + "children": { + "180065.76": { + "request": { + "url": "http://localhost:5173/src/components/ui/Button.css", + "startTime": 26977.79178, + "endTime": 26977.82994, + "responseReceivedTime": 26977.827521000003, + "transferSize": 2160 + } + } + } + }, + "180065.45": { + "request": { + "url": "http://localhost:5173/src/services/newsletterService.ts", + "startTime": 26977.771175, + "endTime": 26977.78971, + "responseReceivedTime": 26977.779832000004, + "transferSize": 1363 + } + }, + "180065.46": { + "request": { + "url": "http://localhost:5173/src/utils/extractApiError.ts", + "startTime": 26977.771422, + "endTime": 26977.791449, + "responseReceivedTime": 26977.781584000004, + "transferSize": 7110 + } + }, + "180065.47": { + "request": { + "url": "http://localhost:5173/src/pages/Newsletter.css", + "startTime": 26977.771682, + "endTime": 26977.791969, + "responseReceivedTime": 26977.788346, + "transferSize": 4998 + } + } + } + }, + "180065.29": { + "request": { + "url": "http://localhost:5173/src/pages/About/index.tsx", + "startTime": 26977.721914, + "endTime": 26977.73562, + "responseReceivedTime": 26977.734995000003, + "transferSize": 5790 + }, + "children": { + "180065.56": { + "request": { + "url": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "startTime": 26977.780624, + "endTime": 26977.805706, + "responseReceivedTime": 26977.803913, + "transferSize": 15734 + } + }, + "180065.57": { + "request": { + "url": "http://localhost:5173/src/pages/About/About.css", + "startTime": 26977.780984, + "endTime": 26977.805115, + "responseReceivedTime": 26977.804496, + "transferSize": 3917 + } + } + } + }, + "180065.30": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "startTime": 26977.722134, + "endTime": 26977.736909, + "responseReceivedTime": 26977.735889, + "transferSize": 4765 + }, + "children": { + "180065.58": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "startTime": 26977.781317, + "endTime": 26977.807403, + "responseReceivedTime": 26977.806122, + "transferSize": 75711 + }, + "children": { + "180065.82": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/Legal.css", + "startTime": 26977.81312, + "endTime": 26977.835077, + "responseReceivedTime": 26977.833569, + "transferSize": 4112 + } + } + } + } + } + }, + "180065.31": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "startTime": 26977.72234, + "endTime": 26977.737101, + "responseReceivedTime": 26977.736369, + "transferSize": 4862 + } + }, + "180065.32": { + "request": { + "url": "http://localhost:5173/src/pages/Article.tsx", + "startTime": 26977.722567, + "endTime": 26977.739908, + "responseReceivedTime": 26977.738413000003, + "transferSize": 35202 + }, + "children": { + "180065.59": { + "request": { + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "startTime": 26977.782299, + "endTime": 26977.810717, + "responseReceivedTime": 26977.807729, + "transferSize": 4592 + }, + "children": { + "180065.84": { + "request": { + "url": "http://localhost:5173/src/components/ui/Avatar.css", + "startTime": 26977.814715, + "endTime": 26977.838985, + "responseReceivedTime": 26977.837928, + "transferSize": 1101 + } + } + } + }, + "180065.60": { + "request": { + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "startTime": 26977.782623, + "endTime": 26977.810397, + "responseReceivedTime": 26977.809504999997, + "transferSize": 3273 + }, + "children": { + "180065.83": { + "request": { + "url": "http://localhost:5173/src/components/ui/Badge.css", + "startTime": 26977.813925, + "endTime": 26977.837626, + "responseReceivedTime": 26977.837032999996, + "transferSize": 2103 + } + } + } + }, + "180065.61": { + "request": { + "url": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "startTime": 26977.782898, + "endTime": 26977.816998, + "responseReceivedTime": 26977.812260000002, + "transferSize": 15920 + } + }, + "180065.62": { + "request": { + "url": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "startTime": 26977.78324, + "endTime": 26977.817153, + "responseReceivedTime": 26977.813376000002, + "transferSize": 11522 + } + }, + "180065.63": { + "request": { + "url": "http://localhost:5173/src/utils/formatDate.ts", + "startTime": 26977.783549, + "endTime": 26977.817321, + "responseReceivedTime": 26977.814152999996, + "transferSize": 5113 + } + }, + "180065.64": { + "request": { + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "startTime": 26977.78386, + "endTime": 26977.817483999996, + "responseReceivedTime": 26977.814946, + "transferSize": 26599 + }, + "children": { + "180065.85": { + "request": { + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "startTime": 26977.823221, + "endTime": 26977.839732, + "responseReceivedTime": 26977.839205999997, + "transferSize": 24618 + }, + "children": { + "180065.95": { + "request": { + "url": "http://localhost:5173/src/components/ui/CommentItem.css", + "startTime": 26977.844547, + "endTime": 26977.848334, + "responseReceivedTime": 26977.846614000006, + "transferSize": 4263 + } + } + } + }, + "180065.86": { + "request": { + "url": "http://localhost:5173/src/services/commentService.ts", + "startTime": 26977.823515, + "endTime": 26977.84094, + "responseReceivedTime": 26977.840427000003, + "transferSize": 2883 + } + } + } + }, + "180065.65": { + "request": { + "url": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "startTime": 26977.784277, + "endTime": 26977.817663, + "responseReceivedTime": 26977.815422, + "transferSize": 6332 + } + }, + "180065.66": { + "request": { + "url": "http://localhost:5173/src/styles/article-body.css", + "startTime": 26977.784563, + "endTime": 26977.816759, + "responseReceivedTime": 26977.815835999998, + "transferSize": 2477 + } + }, + "180065.67": { + "request": { + "url": "http://localhost:5173/src/pages/Article.css", + "startTime": 26977.784823, + "endTime": 26977.819363, + "responseReceivedTime": 26977.818749000002, + "transferSize": 13288 + } + } + } + }, + "180065.33": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "startTime": 26977.722799, + "endTime": 26977.741687, + "responseReceivedTime": 26977.739358, + "transferSize": 13097 + }, + "children": { + "180065.68": { + "request": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "startTime": 26977.785076, + "endTime": 26977.822861, + "responseReceivedTime": 26977.819613000003, + "transferSize": 19518 + }, + "children": { + "180065.87": { + "request": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "startTime": 26977.826509, + "endTime": 26977.843592, + "responseReceivedTime": 26977.84144, + "transferSize": 5342 + }, + "children": { + "180065.96": { + "request": { + "url": "http://localhost:5173/src/assets/seek-white.svg?import", + "startTime": 26977.847226000005, + "endTime": 26977.852625, + "responseReceivedTime": 26977.852085000006, + "transferSize": 10333 + } + }, + "180065.97": { + "request": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "startTime": 26977.847441, + "endTime": 26977.854475, + "responseReceivedTime": 26977.852938000004, + "transferSize": 1532 + } + } + } + }, + "180065.88": { + "request": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.css", + "startTime": 26977.826795, + "endTime": 26977.843781, + "responseReceivedTime": 26977.841954000003, + "transferSize": 4726 + } + } + } + }, + "180065.69": { + "request": { + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "startTime": 26977.785392, + "endTime": 26977.823709, + "responseReceivedTime": 26977.820146000002, + "transferSize": 4439 + }, + "children": { + "180065.89": { + "request": { + "url": "http://localhost:5173/src/components/ui/Input.css", + "startTime": 26977.828154, + "endTime": 26977.843366, + "responseReceivedTime": 26977.842716, + "transferSize": 1768 + } + } + } + }, + "180065.70": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/Auth.css", + "startTime": 26977.785711, + "endTime": 26977.821847, + "responseReceivedTime": 26977.820643, + "transferSize": 3256 + } + } + } + }, + "180065.34": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "startTime": 26977.723039, + "endTime": 26977.741942, + "responseReceivedTime": 26977.740433000003, + "transferSize": 27959 + }, + "children": { + "180065.73": { + "request": { + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "startTime": 26977.786547, + "endTime": 26977.829465, + "responseReceivedTime": 26977.825275, + "transferSize": 13726 + }, + "children": { + "180065.91": { + "request": { + "url": "http://localhost:5173/src/components/ui/Modal.css", + "startTime": 26977.836757, + "endTime": 26977.847758, + "responseReceivedTime": 26977.845355, + "transferSize": 2244 + } + } + } + } + } + }, + "180065.35": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "startTime": 26977.723229, + "endTime": 26977.743994, + "responseReceivedTime": 26977.742978, + "transferSize": 12833 + } + }, + "180065.36": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "startTime": 26977.723402, + "endTime": 26977.742727, + "responseReceivedTime": 26977.740992, + "transferSize": 18222 + }, + "children": { + "180065.71": { + "request": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "startTime": 26977.785996, + "endTime": 26977.824239, + "responseReceivedTime": 26977.821301, + "transferSize": 6890 + }, + "children": { + "180065.90": { + "request": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "startTime": 26977.8289, + "endTime": 26977.847576, + "responseReceivedTime": 26977.844785000005, + "transferSize": 1998 + } + } + } + }, + "180065.72": { + "request": { + "url": "http://localhost:5173/src/utils/passwordRules.ts", + "startTime": 26977.786274, + "endTime": 26977.824044, + "responseReceivedTime": 26977.822363999996, + "transferSize": 3410 + } + } + } + }, + "180065.37": { + "request": { + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "startTime": 26977.723687, + "endTime": 26977.743753, + "responseReceivedTime": 26977.742237000002, + "transferSize": 55832 + }, + "children": { + "180065.74": { + "request": { + "url": "http://localhost:5173/src/pages/Perfil.css", + "startTime": 26977.786866000002, + "endTime": 26977.830218, + "responseReceivedTime": 26977.825872, + "transferSize": 4993 + } + } + } + }, + "180065.38": { + "request": { + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "startTime": 26977.724021, + "endTime": 26977.746619, + "responseReceivedTime": 26977.745963000005, + "transferSize": 10579 + }, + "children": { + "180065.75": { + "request": { + "url": "http://localhost:5173/src/pages/NotFound.css", + "startTime": 26977.787206, + "endTime": 26977.829764, + "responseReceivedTime": 26977.826997, + "transferSize": 2228 + } + } + } + }, + "180065.39": { + "request": { + "url": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "startTime": 26977.724333, + "endTime": 26977.747942, + "responseReceivedTime": 26977.746887999998, + "transferSize": 12734 + } + }, + "180065.40": { + "request": { + "url": "http://localhost:5173/src/router/AdminRoute.tsx", + "startTime": 26977.724587, + "endTime": 26977.748706, + "responseReceivedTime": 26977.747392, + "transferSize": 6218 + } + }, + "180065.41": { + "request": { + "url": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "startTime": 26977.724835, + "endTime": 26977.749153999997, + "responseReceivedTime": 26977.748247000003, + "transferSize": 10378 + } + } + } + } + } + }, + "180065.13": { + "request": { + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "startTime": 26977.643444, + "endTime": 26977.666481, + "responseReceivedTime": 26977.665816000004, + "transferSize": 11746 + }, + "children": { + "180065.16": { + "request": { + "url": "http://localhost:5173/src/services/authService.ts", + "startTime": 26977.671554, + "endTime": 26977.686427, + "responseReceivedTime": 26977.683612, + "transferSize": 5810 + }, + "children": { + "180065.18": { + "request": { + "url": "http://localhost:5173/src/services/api.ts", + "startTime": 26977.690123, + "endTime": 26977.707183, + "responseReceivedTime": 26977.706633, + "transferSize": 7075 + }, + "children": { + "180065.20": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "startTime": 26977.709616, + "endTime": 26977.713347, + "responseReceivedTime": 26977.711978, + "transferSize": 108020 + } + } + } + } + } + } + } + }, + "180065.14": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "startTime": 26977.643753, + "endTime": 26977.672112, + "responseReceivedTime": 26977.670882, + "transferSize": 10883 + } + } + } + } + } + } + }, + "longestChain": { + "duration": 250.6790000014007, + "length": 8, + "transferSize": 1532 + } + }, + "guidanceLevel": 1 + }, + "redirects": { + "id": "redirects", + "title": "Avoid multiple page redirects", + "description": "Redirects introduce additional delays before the page can be loaded. [Learn how to avoid page redirects](https://developer.chrome.com/docs/lighthouse/performance/redirects/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0 + }, + "guidanceLevel": 2 + }, + "image-aspect-ratio": { + "id": "image-aspect-ratio", + "title": "Displays images with correct aspect ratio", + "description": "Image display dimensions should match natural aspect ratio. [Learn more about image aspect ratio](https://developer.chrome.com/docs/lighthouse/best-practices/image-aspect-ratio/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Displayed)" + }, + { + "key": "actualAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Actual)" + } + ], + "items": [] + } + }, + "image-size-responsive": { + "id": "image-size-responsive", + "title": "Serves images with appropriate resolution", + "description": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn how to provide responsive images](https://web.dev/articles/serve-responsive-images).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedSize", + "valueType": "text", + "label": "Displayed size" + }, + { + "key": "actualSize", + "valueType": "text", + "label": "Actual size" + }, + { + "key": "expectedSize", + "valueType": "text", + "label": "Expected size" + } + ], + "items": [] + } + }, + "deprecations": { + "id": "deprecations", + "title": "Avoids deprecated APIs", + "description": "Deprecated APIs will eventually be removed from the browser. [Learn more about deprecated APIs](https://developer.chrome.com/docs/lighthouse/best-practices/deprecations/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "value", + "valueType": "text", + "label": "Deprecation / Warning" + }, + { + "key": "source", + "valueType": "source-location", + "label": "Source" + } + ], + "items": [] + } + }, + "third-party-cookies": { + "id": "third-party-cookies", + "title": "Avoids third-party cookies", + "description": "Third-party cookies may be blocked in some contexts. [Learn more about preparing for third-party cookie restrictions](https://privacysandbox.google.com/cookies/prepare/overview).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [] + } + }, + "mainthread-work-breakdown": { + "id": "mainthread-work-breakdown", + "title": "Minimizes main-thread work", + "description": "Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to minimize main-thread work](https://developer.chrome.com/docs/lighthouse/performance/mainthread-work-breakdown/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 636.516999999995, + "numericUnit": "millisecond", + "displayValue": "0.6 s", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "groupLabel", + "valueType": "text", + "label": "Category" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "group": "other", + "groupLabel": "Other", + "duration": 279.38199999999574 + }, + { + "group": "scriptEvaluation", + "groupLabel": "Script Evaluation", + "duration": 191.1669999999992 + }, + { + "group": "styleLayout", + "groupLabel": "Style & Layout", + "duration": 134.695 + }, + { + "group": "paintCompositeRender", + "groupLabel": "Rendering", + "duration": 13.382999999999996 + }, + { + "group": "scriptParseCompile", + "groupLabel": "Script Parsing & Compilation", + "duration": 9.008999999999997 + }, + { + "group": "garbageCollection", + "groupLabel": "Garbage Collection", + "duration": 6.133999999999996 + }, + { + "group": "parseHTML", + "groupLabel": "Parse HTML & CSS", + "duration": 2.747 + } + ], + "sortedBy": ["duration"] + }, + "guidanceLevel": 1 + }, + "bootup-time": { + "id": "bootup-time", + "title": "JavaScript execution time", + "description": "Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to reduce Javascript execution time](https://developer.chrome.com/docs/lighthouse/performance/bootup-time/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 160.60099999999954, + "numericUnit": "millisecond", + "displayValue": "0.2 s", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "total", + "granularity": 1, + "valueType": "ms", + "label": "Total CPU Time" + }, + { + "key": "scripting", + "granularity": 1, + "valueType": "ms", + "label": "Script Evaluation" + }, + { + "key": "scriptParseCompile", + "granularity": 1, + "valueType": "ms", + "label": "Script Parse" + } + ], + "items": [ + { + "url": "Unattributable", + "total": 220.18799999999555, + "scripting": 10.265999999999995, + "scriptParseCompile": 0 + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "total": 161.09999999999957, + "scripting": 140.60699999999957, + "scriptParseCompile": 1.254 + }, + { + "url": "http://localhost:5173/", + "total": 159.91199999999998, + "scripting": 6.7219999999999995, + "scriptParseCompile": 1.7519999999999998 + } + ], + "summary": { + "wastedMs": 160.60099999999954 + }, + "sortedBy": ["total"] + }, + "guidanceLevel": 1 + }, + "uses-rel-preconnect": { + "id": "uses-rel-preconnect", + "title": "Preconnect to required origins", + "description": "Consider adding `preconnect` or `dns-prefetch` resource hints to establish early connections to important third-party origins. [Learn how to preconnect to required origins](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "warnings": [], + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0, + "sortedBy": ["wastedMs"] + }, + "guidanceLevel": 3 + }, + "font-display": { + "id": "font-display", + "title": "All text remains visible during webfont loads", + "description": "Leverage the `font-display` CSS feature to ensure text is user-visible while webfonts are loading. [Learn more about `font-display`](https://developer.chrome.com/docs/lighthouse/performance/font-display/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "diagnostics": { + "id": "diagnostics", + "title": "Diagnostics", + "description": "Collection of useful page vitals.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "debugdata", + "items": [ + { + "numRequests": 105, + "numScripts": 91, + "numStylesheets": 0, + "numFonts": 4, + "numTasks": 1746, + "numTasksOver10ms": 12, + "numTasksOver25ms": 5, + "numTasksOver50ms": 3, + "numTasksOver100ms": 1, + "numTasksOver500ms": 0, + "rtt": 0.05925, + "throughput": 243559923.63666666, + "maxRtt": 0.05925, + "maxServerLatency": 3.33775, + "totalByteWeight": 2930513, + "totalTaskTime": 636.5170000000016, + "mainDocumentTransferSize": 2584 + } + ] + } + }, + "network-requests": { + "id": "network-requests", + "title": "Network Requests", + "description": "Lists the network requests that were made during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + }, + { + "key": "networkRequestTime", + "valueType": "ms", + "granularity": 1, + "label": "Network Request Time" + }, + { + "key": "networkEndTime", + "valueType": "ms", + "granularity": 1, + "label": "Network End Time" + }, + { + "key": "transferSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Transfer Size" + }, + { + "key": "resourceSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Resource Size" + }, + { + "key": "statusCode", + "valueType": "text", + "label": "Status Code" + }, + { + "key": "mimeType", + "valueType": "text", + "label": "MIME Type" + }, + { + "key": "resourceType", + "valueType": "text", + "label": "Resource Type" + } + ], + "items": [ + { + "url": "http://localhost:5173/", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 0, + "networkRequestTime": 2.361999999731779, + "networkEndTime": 13.237999998033047, + "finished": true, + "transferSize": 2584, + "resourceSize": 2330, + "statusCode": 200, + "mimeType": "text/html", + "resourceType": "Document", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/@vite/client", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 21.090999998152256, + "networkRequestTime": 28.171999998390675, + "networkEndTime": 104.09899999946356, + "finished": true, + "transferSize": 210114, + "resourceSize": 209821, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/main.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 21.84299999848008, + "networkRequestTime": 28.59299999848008, + "networkEndTime": 32.46800000220537, + "finished": true, + "transferSize": 2330, + "resourceSize": 2040, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 21.988000001758337, + "networkRequestTime": 21.988000001758337, + "networkEndTime": 88.66399999707937, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "Script", + "priority": "Low", + "experimentalFromMainFrame": true, + "entity": "vlibras.gov.br" + }, + { + "url": "http://localhost:5173/@react-refresh", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 34.95600000023842, + "networkRequestTime": 35.515999998897314, + "networkEndTime": 61.7090000025928, + "finished": true, + "transferSize": 112187, + "resourceSize": 111894, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 38.20099999755621, + "networkRequestTime": 39.8559999987483, + "networkEndTime": 70.34400000050664, + "finished": true, + "transferSize": 38613, + "resourceSize": 38303, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 38.46099999919534, + "networkRequestTime": 40.43800000101328, + "networkEndTime": 85.3669999986887, + "finished": true, + "transferSize": 821294, + "resourceSize": 820982, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/styles/global.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 38.76799999922514, + "networkRequestTime": 41.02600000053644, + "networkEndTime": 85.19799999892712, + "finished": true, + "transferSize": 16873, + "resourceSize": 16581, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/App.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 38.94800000265241, + "networkRequestTime": 41.51500000059605, + "networkEndTime": 104.94199999794364, + "finished": true, + "transferSize": 2529, + "resourceSize": 2239, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 39.12000000104308, + "networkRequestTime": 42.01000000163913, + "networkEndTime": 65.04699999839067, + "finished": true, + "transferSize": 11746, + "resourceSize": 11454, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 39.33500000089407, + "networkRequestTime": 42.318999998271465, + "networkEndTime": 70.67799999937415, + "finished": true, + "transferSize": 10883, + "resourceSize": 10573, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/site.webmanifest", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 40.305999998003244, + "networkRequestTime": 70.85099999979138, + "networkEndTime": 113.93999999761581, + "finished": true, + "transferSize": 796, + "resourceSize": 517, + "statusCode": 200, + "mimeType": "application/manifest+json", + "resourceType": "Manifest", + "priority": "Medium", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/authService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 69.07299999892712, + "networkRequestTime": 70.12000000104308, + "networkEndTime": 84.99300000071526, + "finished": true, + "transferSize": 5810, + "resourceSize": 5520, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 75.51999999955297, + "networkRequestTime": 76.03400000184774, + "networkEndTime": 84.21099999919534, + "finished": true, + "transferSize": 7922, + "resourceSize": 7614, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/api.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 88.04800000041723, + "networkRequestTime": 88.68899999931455, + "networkEndTime": 105.74899999797344, + "finished": true, + "transferSize": 7075, + "resourceSize": 6785, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 104.09100000187755, + "networkRequestTime": 106.12700000032783, + "networkEndTime": 110.1820000000298, + "finished": true, + "transferSize": 14940, + "resourceSize": 14630, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 107.45300000160933, + "networkRequestTime": 108.1820000000298, + "networkEndTime": 111.9129999987781, + "finished": true, + "transferSize": 108020, + "resourceSize": 107708, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 108.7039999999106, + "networkRequestTime": 109.72399999946356, + "networkEndTime": 117.7690000012517, + "finished": true, + "transferSize": 3768, + "resourceSize": 3478, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 110.46000000089407, + "networkRequestTime": 111.37800000235438, + "networkEndTime": 115.24599999934435, + "finished": true, + "transferSize": 21611, + "resourceSize": 21319, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 118.58999999985099, + "networkRequestTime": 119.04000000283122, + "networkEndTime": 132.39800000190735, + "finished": true, + "transferSize": 399267, + "resourceSize": 398955, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 118.7800000011921, + "networkRequestTime": 119.32499999925494, + "networkEndTime": 129.71399999782443, + "finished": true, + "transferSize": 3458, + "resourceSize": 3150, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 119.0029999986291, + "networkRequestTime": 119.58199999853969, + "networkEndTime": 131.20199999958277, + "finished": true, + "transferSize": 9815, + "resourceSize": 9524, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Home.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 119.17300000041723, + "networkRequestTime": 119.83100000023842, + "networkEndTime": 131.42599999904633, + "finished": true, + "transferSize": 22299, + "resourceSize": 22007, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/News.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 119.33900000154972, + "networkRequestTime": 120.05099999904633, + "networkEndTime": 131.77899999916553, + "finished": true, + "transferSize": 31303, + "resourceSize": 31011, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 119.46399999782443, + "networkRequestTime": 120.25799999758601, + "networkEndTime": 131.9699999988079, + "finished": true, + "transferSize": 19118, + "resourceSize": 18826, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/About/index.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 119.64599999785423, + "networkRequestTime": 120.48000000044703, + "networkEndTime": 134.18600000068545, + "finished": true, + "transferSize": 5790, + "resourceSize": 5500, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 119.79800000041723, + "networkRequestTime": 120.69999999925494, + "networkEndTime": 135.47499999776483, + "finished": true, + "transferSize": 4765, + "resourceSize": 4475, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 119.97899999842048, + "networkRequestTime": 120.90599999949336, + "networkEndTime": 135.66699999943376, + "finished": true, + "transferSize": 4862, + "resourceSize": 4572, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Article.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 120.10499999672174, + "networkRequestTime": 121.1330000013113, + "networkEndTime": 138.47399999946356, + "finished": true, + "transferSize": 35202, + "resourceSize": 34910, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 120.30299999937415, + "networkRequestTime": 121.36499999836087, + "networkEndTime": 140.25300000235438, + "finished": true, + "transferSize": 13097, + "resourceSize": 12805, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 120.48699999973178, + "networkRequestTime": 121.60500000044703, + "networkEndTime": 140.5080000013113, + "finished": true, + "transferSize": 27959, + "resourceSize": 27667, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 120.6659999974072, + "networkRequestTime": 121.79499999806285, + "networkEndTime": 142.5599999986589, + "finished": true, + "transferSize": 12833, + "resourceSize": 12541, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 120.84699999913573, + "networkRequestTime": 121.96799999848008, + "networkEndTime": 141.2930000014603, + "finished": true, + "transferSize": 18222, + "resourceSize": 17930, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 121.00799999758601, + "networkRequestTime": 122.25300000235438, + "networkEndTime": 142.31899999827147, + "finished": true, + "transferSize": 55832, + "resourceSize": 55540, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 121.2390000000596, + "networkRequestTime": 122.5870000012219, + "networkEndTime": 145.1850000023842, + "finished": true, + "transferSize": 10579, + "resourceSize": 10287, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 121.4049999974668, + "networkRequestTime": 122.89899999648333, + "networkEndTime": 146.5080000013113, + "finished": true, + "transferSize": 12734, + "resourceSize": 12442, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/router/AdminRoute.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 121.55200000107288, + "networkRequestTime": 123.15300000086427, + "networkEndTime": 147.2719999998808, + "finished": true, + "transferSize": 6218, + "resourceSize": 5928, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 121.79399999976158, + "networkRequestTime": 123.40100000053644, + "networkEndTime": 147.7199999988079, + "finished": true, + "transferSize": 10378, + "resourceSize": 10086, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 167.4809999987483, + "networkRequestTime": 168.21299999952316, + "networkEndTime": 189.33599999919534, + "finished": true, + "transferSize": 4791, + "resourceSize": 4501, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 167.6389999985695, + "networkRequestTime": 169.38500000163913, + "networkEndTime": 186.507999997586, + "finished": true, + "transferSize": 3750, + "resourceSize": 3460, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/newsletterService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 167.7930000014603, + "networkRequestTime": 169.74100000038743, + "networkEndTime": 188.27600000053644, + "finished": true, + "transferSize": 1363, + "resourceSize": 1073, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/extractApiError.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 168.34599999710917, + "networkRequestTime": 169.98800000175834, + "networkEndTime": 190.01500000059605, + "finished": true, + "transferSize": 7110, + "resourceSize": 6820, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Newsletter.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 168.55499999970198, + "networkRequestTime": 170.24799999967217, + "networkEndTime": 190.535000000149, + "finished": true, + "transferSize": 4998, + "resourceSize": 4707, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ErrorFallback.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 169.15900000184774, + "networkRequestTime": 170.50100000202656, + "networkEndTime": 189.75699999928474, + "finished": true, + "transferSize": 3123, + "resourceSize": 2833, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 169.45199999958277, + "networkRequestTime": 170.78399999812245, + "networkEndTime": 192.36299999803305, + "finished": true, + "transferSize": 12632, + "resourceSize": 12340, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/categoryVariant.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 169.6550000011921, + "networkRequestTime": 171.02800000086427, + "networkEndTime": 193.14499999955297, + "finished": true, + "transferSize": 2562, + "resourceSize": 2272, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/articleService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 169.8260000012815, + "networkRequestTime": 176.07000000029802, + "networkEndTime": 196.94099999964237, + "finished": true, + "transferSize": 9838, + "resourceSize": 9548, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/News.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 169.98299999907613, + "networkRequestTime": 176.402000002563, + "networkEndTime": 195.93099999800324, + "finished": true, + "transferSize": 1882, + "resourceSize": 1592, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 170.48000000044703, + "networkRequestTime": 176.69200000166893, + "networkEndTime": 197.2800000011921, + "finished": true, + "transferSize": 35878, + "resourceSize": 35586, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 170.71900000050664, + "networkRequestTime": 176.9400000013411, + "networkEndTime": 198.26799999922514, + "finished": true, + "transferSize": 689, + "resourceSize": 401, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Home.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 170.92799999937415, + "networkRequestTime": 177.19199999794364, + "networkEndTime": 202.22199999913573, + "finished": true, + "transferSize": 9483, + "resourceSize": 9192, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 171.32299999892712, + "networkRequestTime": 179.1899999976158, + "networkEndTime": 204.2719999998808, + "finished": true, + "transferSize": 15734, + "resourceSize": 15442, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/About/About.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 171.46599999815226, + "networkRequestTime": 179.55000000074506, + "networkEndTime": 203.68099999800324, + "finished": true, + "transferSize": 3917, + "resourceSize": 3627, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 172.64599999785423, + "networkRequestTime": 179.8830000013113, + "networkEndTime": 205.96899999678135, + "finished": true, + "transferSize": 75711, + "resourceSize": 75419, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173, + "networkRequestTime": 180.86499999836087, + "networkEndTime": 209.2829999998212, + "finished": true, + "transferSize": 4592, + "resourceSize": 4302, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.1519999988377, + "networkRequestTime": 181.18899999931455, + "networkEndTime": 208.96299999952316, + "finished": true, + "transferSize": 3273, + "resourceSize": 2983, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.3260000012815, + "networkRequestTime": 181.46400000154972, + "networkEndTime": 215.56399999931455, + "finished": true, + "transferSize": 15920, + "resourceSize": 15628, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.4810000024736, + "networkRequestTime": 181.80600000172853, + "networkEndTime": 215.71900000050664, + "finished": true, + "transferSize": 11522, + "resourceSize": 11230, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/formatDate.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.64900000020862, + "networkRequestTime": 182.11499999836087, + "networkEndTime": 215.88699999824166, + "finished": true, + "transferSize": 5113, + "resourceSize": 4823, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.8179999999702, + "networkRequestTime": 182.42599999904633, + "networkEndTime": 216.04999999701977, + "finished": true, + "transferSize": 26599, + "resourceSize": 26307, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.98799999803305, + "networkRequestTime": 182.84299999848008, + "networkEndTime": 216.22900000214577, + "finished": true, + "transferSize": 6332, + "resourceSize": 6042, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/styles/article-body.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 174.16999999806285, + "networkRequestTime": 183.12900000065565, + "networkEndTime": 215.32499999925494, + "finished": true, + "transferSize": 2477, + "resourceSize": 2187, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Article.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 174.30600000172853, + "networkRequestTime": 183.38900000229478, + "networkEndTime": 217.92899999767542, + "finished": true, + "transferSize": 13288, + "resourceSize": 12996, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 175.09599999710917, + "networkRequestTime": 183.64200000092387, + "networkEndTime": 221.42700000107288, + "finished": true, + "transferSize": 19518, + "resourceSize": 19226, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 175.32999999821186, + "networkRequestTime": 183.95800000056624, + "networkEndTime": 222.27499999850988, + "finished": true, + "transferSize": 4439, + "resourceSize": 4149, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/Auth.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 175.49500000104308, + "networkRequestTime": 184.2769999988377, + "networkEndTime": 220.4129999987781, + "finished": true, + "transferSize": 3256, + "resourceSize": 2966, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 176.1030000001192, + "networkRequestTime": 184.56199999898672, + "networkEndTime": 222.80499999970198, + "finished": true, + "transferSize": 6890, + "resourceSize": 6600, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/passwordRules.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 176.35799999907613, + "networkRequestTime": 184.8399999961257, + "networkEndTime": 222.60999999940395, + "finished": true, + "transferSize": 3410, + "resourceSize": 3120, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 176.79299999773502, + "networkRequestTime": 185.11299999803305, + "networkEndTime": 228.03099999949336, + "finished": true, + "transferSize": 13726, + "resourceSize": 13434, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Perfil.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 177.1330000013113, + "networkRequestTime": 185.4320000000298, + "networkEndTime": 228.78399999812245, + "finished": true, + "transferSize": 4993, + "resourceSize": 4702, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/NotFound.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 177.45199999958277, + "networkRequestTime": 185.7719999998808, + "networkEndTime": 228.32999999821186, + "finished": true, + "transferSize": 2228, + "resourceSize": 1938, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Button.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 189.56399999931455, + "networkRequestTime": 190.34600000083447, + "networkEndTime": 228.50600000098348, + "finished": true, + "transferSize": 2160, + "resourceSize": 1870, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 192.39900000020862, + "networkRequestTime": 194.06199999898672, + "networkEndTime": 228.92500000074506, + "finished": true, + "transferSize": 21741, + "resourceSize": 21449, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 192.5650000013411, + "networkRequestTime": 194.35700000077486, + "networkEndTime": 229.05800000205636, + "finished": true, + "transferSize": 21715, + "resourceSize": 21423, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/PageLayout.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 192.76300000026822, + "networkRequestTime": 194.95800000056624, + "networkEndTime": 232.62800000235438, + "finished": true, + "transferSize": 1789, + "resourceSize": 1499, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCard.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 199.40900000184774, + "networkRequestTime": 200.87700000032783, + "networkEndTime": 233.28599999845028, + "finished": true, + "transferSize": 6902, + "resourceSize": 6611, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 202.94099999964237, + "networkRequestTime": 204.0410000011325, + "networkEndTime": 232.97399999946356, + "finished": true, + "transferSize": 3997, + "resourceSize": 3707, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/Legal.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 210.90300000086427, + "networkRequestTime": 211.68599999696016, + "networkEndTime": 233.64299999922514, + "finished": true, + "transferSize": 4112, + "resourceSize": 3822, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Badge.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 211.29999999701977, + "networkRequestTime": 212.49099999666214, + "networkEndTime": 236.19200000166893, + "finished": true, + "transferSize": 2103, + "resourceSize": 1813, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Avatar.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 212.50600000098348, + "networkRequestTime": 213.28099999949336, + "networkEndTime": 237.55099999904633, + "finished": true, + "transferSize": 1101, + "resourceSize": 812, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 221.18800000101328, + "networkRequestTime": 221.78699999675155, + "networkEndTime": 238.29800000041723, + "finished": true, + "transferSize": 24618, + "resourceSize": 24326, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/commentService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 221.4079999998212, + "networkRequestTime": 222.08100000023842, + "networkEndTime": 239.5059999972582, + "finished": true, + "transferSize": 2883, + "resourceSize": 2593, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 224.09599999710917, + "networkRequestTime": 225.07499999925494, + "networkEndTime": 242.1579999998212, + "finished": true, + "transferSize": 5342, + "resourceSize": 5052, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/AuthLayout.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 224.31099999696016, + "networkRequestTime": 225.3610000014305, + "networkEndTime": 242.34699999913573, + "finished": true, + "transferSize": 4726, + "resourceSize": 4435, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Input.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 225.98699999973178, + "networkRequestTime": 226.7199999988079, + "networkEndTime": 241.9320000000298, + "finished": true, + "transferSize": 1768, + "resourceSize": 1478, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 226.5329999998212, + "networkRequestTime": 227.46600000187755, + "networkEndTime": 246.14200000092387, + "finished": true, + "transferSize": 1998, + "resourceSize": 1708, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Modal.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 234.45300000160933, + "networkRequestTime": 235.32299999892712, + "networkEndTime": 246.32400000095367, + "finished": true, + "transferSize": 2244, + "resourceSize": 1954, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 234.94700000062585, + "networkRequestTime": 237.1300000026822, + "networkEndTime": 246.67599999904633, + "finished": true, + "transferSize": 20091, + "resourceSize": 19799, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Navbar.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 235.12299999967217, + "networkRequestTime": 237.37800000235438, + "networkEndTime": 249.53900000080466, + "finished": true, + "transferSize": 8587, + "resourceSize": 8296, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Footer.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 237.3589999973774, + "networkRequestTime": 238.74900000169873, + "networkEndTime": 249.78399999812245, + "finished": true, + "transferSize": 5121, + "resourceSize": 4830, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/CommentItem.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 242.472999997437, + "networkRequestTime": 243.11300000175834, + "networkEndTime": 246.89999999850988, + "finished": true, + "transferSize": 4263, + "resourceSize": 3973, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/assets/seek-white.svg?import", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 245.07100000232458, + "networkRequestTime": 245.79200000315905, + "networkEndTime": 251.19099999964237, + "finished": true, + "transferSize": 10333, + "resourceSize": 10042, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 245.28600000217557, + "networkRequestTime": 246.00700000301003, + "networkEndTime": 253.0410000011325, + "finished": true, + "transferSize": 1532, + "resourceSize": 1242, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 438.51500000059605, + "networkRequestTime": 439.7119999974966, + "networkEndTime": 443.5029999986291, + "finished": true, + "transferSize": 116617, + "resourceSize": 116344, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "Low", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "data:image/svg+xml,%3csvg%20id='Camada_1'%20data-name='Camada%201'%20xmlns='http://www.w3.org/2000/…", + "sessionTargetType": "page", + "protocol": "data", + "rendererStartTime": 446.9589999988675, + "networkRequestTime": 446.9589999988675, + "networkEndTime": 447.65399999916553, + "finished": true, + "transferSize": 0, + "resourceSize": 1581, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "Low", + "experimentalFromMainFrame": true + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/newsreader/files/newsreader-latin-wght-normal.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 479.5379999987781, + "networkRequestTime": 592.9199999980628, + "networkEndTime": 600.9429999999702, + "finished": true, + "transferSize": 58404, + "resourceSize": 58084, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/inter/files/inter-latin-wght-normal.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 493.3070000000298, + "networkRequestTime": 593.3729999996722, + "networkEndTime": 600.6640000008047, + "finished": true, + "transferSize": 48576, + "resourceSize": 48256, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/montserrat/files/montserrat-latin-wght-normal.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 505.4219999983907, + "networkRequestTime": 593.7479999996722, + "networkEndTime": 600.820000000298, + "finished": true, + "transferSize": 38276, + "resourceSize": 37956, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/newsreader/files/newsreader-latin-wght-italic.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 505.58299999684095, + "networkRequestTime": 594.0949999988079, + "networkEndTime": 601.1040000021458, + "finished": true, + "transferSize": 64840, + "resourceSize": 64520, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 607.8610000014305, + "networkRequestTime": 607.8610000014305, + "networkEndTime": 641.9730000011623, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/auth/me/", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 610.1869999989867, + "networkRequestTime": 610.1869999989867, + "networkEndTime": 642.7199999988079, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 613.7939999997616, + "networkRequestTime": 613.7939999997616, + "networkEndTime": 643.3059999980032, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/auth/me/", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 615.5140000022948, + "networkRequestTime": 615.5140000022948, + "networkEndTime": 643.8850000016391, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 639.6390000022948, + "networkRequestTime": 640.1339999996126, + "networkEndTime": 642.8139999993145, + "finished": true, + "transferSize": 684, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 644.9569999985397, + "networkRequestTime": 645.5080000013113, + "networkEndTime": 647.6180000007153, + "finished": true, + "transferSize": 127, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + } + ], + "debugData": { + "type": "debugdata", + "networkStartTimeTs": 26977601434, + "initiators": [ + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 8, + "columnNumber": 46 + }, + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 41, + "columnNumber": 46 + }, + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 45, + "columnNumber": 69 + }, + { + "type": "script", + "url": "http://localhost:5173/", + "lineNumber": 3, + "columnNumber": 63 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 0, + "columnNumber": 228 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 1, + "columnNumber": 47 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 3, + "columnNumber": 16 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 4, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 6, + "columnNumber": 51 + }, + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 54, + "columnNumber": 1 + }, + { + "type": "script", + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "lineNumber": 11, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "lineNumber": 0, + "columnNumber": 35 + }, + { + "type": "script", + "url": "http://localhost:5173/src/services/authService.ts", + "lineNumber": 0, + "columnNumber": 16 + }, + { + "type": "script", + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "lineNumber": 2, + "columnNumber": 39 + }, + { + "type": "script", + "url": "http://localhost:5173/src/services/api.ts", + "lineNumber": 10, + "columnNumber": 18 + }, + { + "type": "script", + "url": "http://localhost:5173/@vite/client", + "lineNumber": 0, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/App.tsx", + "lineNumber": 0, + "columnNumber": 227 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 1, + "columnNumber": 45 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 2, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 3, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 4, + "columnNumber": 21 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 5, + "columnNumber": 21 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 6, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 7, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 8, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 9, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 10, + "columnNumber": 24 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 11, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 12, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 13, + "columnNumber": 31 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 14, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 15, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 16, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 17, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 18, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 19, + "columnNumber": 34 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 1, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 2, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 3, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 4, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 5, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "lineNumber": 1, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/News.tsx", + "lineNumber": 3, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/News.tsx", + "lineNumber": 5, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/News.tsx", + "lineNumber": 6, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/News.tsx", + "lineNumber": 7, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 4, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 6, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 7, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/About/index.tsx", + "lineNumber": 1, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/About/index.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "lineNumber": 1, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 3, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 4, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 6, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 7, + "columnNumber": 36 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 8, + "columnNumber": 31 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 9, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 12, + "columnNumber": 34 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 13, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 14, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "lineNumber": 2, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "lineNumber": 4, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "lineNumber": 7, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "lineNumber": 6, + "columnNumber": 34 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "lineNumber": 7, + "columnNumber": 33 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "lineNumber": 5, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "lineNumber": 21, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "lineNumber": 11, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "lineNumber": 0, + "columnNumber": 225 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "lineNumber": 0, + "columnNumber": 249 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "lineNumber": 1, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "lineNumber": 5, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "lineNumber": 23, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "lineNumber": 0, + "columnNumber": 224 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "lineNumber": 10, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "lineNumber": 15, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "lineNumber": 16, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "lineNumber": 2, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "lineNumber": 8, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "lineNumber": 0, + "columnNumber": 224 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "lineNumber": 1, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "lineNumber": 4, + "columnNumber": 31 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "lineNumber": 6, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "lineNumber": 6, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "lineNumber": 7, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "lineNumber": 0, + "columnNumber": 244 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "lineNumber": 1, + "columnNumber": 7 + } + ] + } + } + }, + "network-rtt": { + "id": "network-rtt", + "title": "Network Round Trip Times", + "description": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more about the Round Trip Time](https://hpbn.co/primer-on-latency-and-bandwidth/).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 0.05925, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "rtt", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:5173", + "rtt": 0.05925 + } + ], + "sortedBy": ["rtt"] + } + }, + "network-server-latency": { + "id": "network-server-latency", + "title": "Server Backend Latencies", + "description": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more about server response time](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 3.33775, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "serverResponseTime", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:5173", + "serverResponseTime": 3.33775 + } + ], + "sortedBy": ["serverResponseTime"] + } + }, + "main-thread-tasks": { + "id": "main-thread-tasks", + "title": "Tasks", + "description": "Lists the toplevel main thread tasks that executed during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "End Time" + } + ], + "items": [ + { + "duration": 12.27, + "startTime": 18.266 + }, + { + "duration": 10.976, + "startTime": 125.695 + }, + { + "duration": 7.012, + "startTime": 136.7 + }, + { + "duration": 7.677, + "startTime": 143.727 + }, + { + "duration": 76.705, + "startTime": 256.514 + }, + { + "duration": 28.985, + "startTime": 333.237 + }, + { + "duration": 83.182, + "startTime": 368.055 + }, + { + "duration": 22.331, + "startTime": 451.294 + }, + { + "duration": 120.669, + "startTime": 473.772 + }, + { + "duration": 23.442, + "startTime": 597.971 + }, + { + "duration": 13.308, + "startTime": 626.898 + }, + { + "duration": 32.512, + "startTime": 648.457 + }, + { + "duration": 10.401, + "startTime": 680.98 + }, + { + "duration": 6.231, + "startTime": 706.249 + }, + { + "duration": 10.863, + "startTime": 1383.994 + } + ] + } + }, + "metrics": { + "id": "metrics", + "title": "Metrics", + "description": "Collects all available metrics.", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 3137, + "numericUnit": "millisecond", + "details": { + "type": "debugdata", + "items": [ + { + "firstContentfulPaint": 1876, + "largestContentfulPaint": 3137, + "interactive": 3137, + "speedIndex": 1876, + "totalBlockingTime": 0, + "maxPotentialFID": 42, + "cumulativeLayoutShift": 0.15287, + "cumulativeLayoutShiftMainFrame": 0.15287, + "timeToFirstByte": 123, + "observedTimeOrigin": 0, + "observedTimeOriginTs": 26977599325, + "observedNavigationStart": 0, + "observedNavigationStartTs": 26977599325, + "observedFirstPaint": 369, + "observedFirstPaintTs": 26977968680, + "observedFirstContentfulPaint": 622, + "observedFirstContentfulPaintTs": 26978221122, + "observedFirstContentfulPaintAllFrames": 622, + "observedFirstContentfulPaintAllFramesTs": 26978221122, + "observedLargestContentfulPaint": 622, + "observedLargestContentfulPaintTs": 26978221122, + "observedLargestContentfulPaintAllFrames": 622, + "observedLargestContentfulPaintAllFramesTs": 26978221122, + "observedTraceEnd": 3016, + "observedTraceEndTs": 26980615308, + "observedLoad": 366, + "observedLoadTs": 26977965717, + "observedDomContentLoaded": 365, + "observedDomContentLoadedTs": 26977964587, + "observedCumulativeLayoutShift": 0.15287, + "observedCumulativeLayoutShiftMainFrame": 0.15287, + "observedFirstVisualChange": 369, + "observedFirstVisualChangeTs": 26977968325, + "observedLastVisualChange": 667, + "observedLastVisualChangeTs": 26978266325, + "observedSpeedIndex": 509, + "observedSpeedIndexTs": 26978108824 + }, + { + "lcpInvalidated": false + } + ] + } + }, + "resource-summary": { + "id": "resource-summary", + "title": "Resources Summary", + "description": "Aggregates all network requests and groups them by type", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Resource Type" + }, + { + "key": "requestCount", + "valueType": "numeric", + "label": "Requests" + }, + { + "key": "transferSize", + "valueType": "bytes", + "label": "Transfer Size" + } + ], + "items": [ + { + "resourceType": "total", + "label": "Total", + "requestCount": 104, + "transferSize": 2930513 + }, + { + "resourceType": "script", + "label": "Script", + "requestCount": 91, + "transferSize": 2599609 + }, + { + "resourceType": "font", + "label": "Font", + "requestCount": 4, + "transferSize": 210096 + }, + { + "resourceType": "image", + "label": "Image", + "requestCount": 1, + "transferSize": 116617 + }, + { + "resourceType": "document", + "label": "Document", + "requestCount": 1, + "transferSize": 2584 + }, + { + "resourceType": "other", + "label": "Other", + "requestCount": 7, + "transferSize": 1607 + }, + { + "resourceType": "stylesheet", + "label": "Stylesheet", + "requestCount": 0, + "transferSize": 0 + }, + { + "resourceType": "media", + "label": "Media", + "requestCount": 0, + "transferSize": 0 + }, + { + "resourceType": "third-party", + "label": "Third-party", + "requestCount": 1, + "transferSize": 0 + } + ] + } + }, + "third-party-summary": { + "id": "third-party-summary", + "title": "Minimize third-party usage", + "description": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn how to minimize third-party impact](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "Third-party code blocked the main thread for 0 ms", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "Third-Party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer Size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "blockingTime", + "granularity": 1, + "valueType": "ms", + "label": "Main-Thread Blocking Time", + "subItemsHeading": { + "key": "blockingTime" + } + } + ], + "items": [ + { + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0, + "entity": "vlibras.gov.br", + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0 + } + ] + } + } + ], + "isEntityGrouped": true + }, + "guidanceLevel": 1 + }, + "third-party-facades": { + "id": "third-party-facades", + "title": "Lazy load third-party resources with facades", + "description": "Some third-party embeds can be lazy loaded. Consider replacing them with a facade until they are required. [Learn how to defer third-parties with a facade](https://developer.chrome.com/docs/lighthouse/performance/third-party-facades/).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "TBT": 0 + }, + "guidanceLevel": 3 + }, + "largest-contentful-paint-element": { + "id": "largest-contentful-paint-element", + "title": "Largest Contentful Paint element", + "description": "This is the largest contentful element painted within the viewport. [Learn more about the Largest Contentful Paint element](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "3,140 ms", + "metricSavings": { + "LCP": 1950 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "Element" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 152, + "bottom": 396, + "left": 92, + "right": 686, + "width": 595, + "height": 244 + }, + "snippet": "

", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + } + ] + }, + { + "type": "table", + "headings": [ + { + "key": "phase", + "valueType": "text", + "label": "Phase" + }, + { + "key": "percent", + "valueType": "text", + "label": "% of LCP" + }, + { + "key": "timing", + "valueType": "ms", + "label": "Timing" + } + ], + "items": [ + { + "phase": "TTFB", + "timing": 123.33775, + "percent": "4%" + }, + { + "phase": "Load Delay", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Load Time", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Render Delay", + "timing": 3013.351, + "percent": "96%" + } + ] + } + ] + }, + "guidanceLevel": 1 + }, + "lcp-lazy-loaded": { + "id": "lcp-lazy-loaded", + "title": "Largest Contentful Paint image was not lazily loaded", + "description": "Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. [Learn more about optimal lazy loading](https://web.dev/articles/lcp-lazy-loading).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 3 + }, + "layout-shifts": { + "id": "layout-shifts", + "title": "Avoid large layout shifts", + "description": "These are the largest layout shifts observed on the page. Each table item represents a single layout shift, and shows the element that shifted the most. Below each item are possible root causes that led to the layout shift. Some of these layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "2 layout shifts found", + "metricSavings": { + "CLS": 0.153 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-1-SECTION", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION", + "selector": "div#root > div.page-layout > main#main > section.home-news", + "boundingRect": { + "top": 539, + "bottom": 910, + "left": 0, + "right": 1335, + "width": 1335, + "height": 372 + }, + "snippet": "
", + "nodeLabel": "Últimas Notícias\nVer todas →\n\nNenhum artigo disponível no momento.\n\nVer todas a…" + }, + "score": 0.145676 + }, + { + "node": { + "type": "node", + "lhId": "page-4-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION,0,DIV,2,DIV", + "selector": "main#main > section.home-news > div.container > div.home-load-more", + "boundingRect": { + "top": 798, + "bottom": 846, + "left": 92, + "right": 1244, + "width": 1152, + "height": 48 + }, + "snippet": "
", + "nodeLabel": "Ver todas as notícias" + }, + "score": 0.007194, + "subItems": { + "type": "subitems", + "items": [ + { + "extra": { + "type": "node", + "lhId": "page-6-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1242, + "bottom": 1282, + "left": 92, + "right": 229, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + }, + "cause": "Media element lacking an explicit size" + } + ] + } + } + ] + }, + "guidanceLevel": 2 + }, + "long-tasks": { + "id": "long-tasks", + "title": "Avoid long main-thread tasks", + "description": "Lists the longest tasks on the main thread, useful for identifying worst contributors to input delay. [Learn how to avoid long main-thread tasks](https://web.dev/articles/optimize-long-tasks)", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "2 long tasks found", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Duration" + } + ], + "items": [ + { + "url": "Unattributable", + "duration": 77.00000000000003, + "startTime": 191.33775 + }, + { + "url": "http://localhost:5173/", + "duration": 60, + "startTime": 279.33775 + } + ], + "sortedBy": ["duration"], + "skipSumming": ["startTime"], + "debugData": { + "type": "debugdata", + "urls": ["Unattributable", "http://localhost:5173/"], + "tasks": [ + { + "urlIndex": 0, + "startTime": 191.3, + "duration": 77, + "other": 77 + }, + { + "urlIndex": 1, + "startTime": 279.3, + "duration": 60, + "other": 60, + "paintCompositeRender": 0, + "styleLayout": 0 + } + ] + } + }, + "guidanceLevel": 1 + }, + "non-composited-animations": { + "id": "non-composited-animations", + "title": "Avoid non-composited animations", + "description": "Animations which are not composited can be janky and increase CLS. [Learn how to avoid non-composited animations](https://developer.chrome.com/docs/lighthouse/performance/non-composited-animations/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "failureReason", + "valueType": "text" + }, + "label": "Element" + } + ], + "items": [] + }, + "guidanceLevel": 2 + }, + "unsized-images": { + "id": "unsized-images", + "title": "Image elements do not have explicit `width` and `height`", + "description": "Set an explicit width and height on image elements to reduce layout shifts and improve CLS. [Learn how to set image dimensions](https://web.dev/articles/optimize-cls#images_without_dimensions)", + "score": 0.5, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [ + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "node": { + "type": "node", + "lhId": "1-27-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,1,HEADER,0,DIV,0,A,0,IMG", + "selector": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img", + "boundingRect": { + "top": 13, + "bottom": 51, + "left": 92, + "right": 222, + "width": 130, + "height": 38 + }, + "snippet": "\"\"", + "nodeLabel": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img" + } + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "node": { + "type": "node", + "lhId": "1-28-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV,1,IMG", + "selector": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark", + "boundingRect": { + "top": 262, + "bottom": 348, + "left": 838, + "right": 1132, + "width": 295, + "height": 86 + }, + "snippet": "\"\"", + "nodeLabel": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark" + } + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "node": { + "type": "node", + "lhId": "1-29-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1242, + "bottom": 1282, + "left": 92, + "right": 229, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + } + } + ] + }, + "guidanceLevel": 4 + }, + "valid-source-maps": { + "id": "valid-source-maps", + "title": "Page has valid source maps", + "description": "Source maps translate minified code to the original source code. This helps developers debug in production. In addition, Lighthouse is able to provide further insights. Consider deploying source maps to take advantage of these benefits. [Learn more about source maps](https://developer.chrome.com/docs/devtools/javascript/source-maps/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "scriptUrl", + "valueType": "url", + "subItemsHeading": { + "key": "error" + }, + "label": "URL" + }, + { + "key": "sourceMapUrl", + "valueType": "url", + "label": "Map URL" + } + ], + "items": [ + { + "scriptUrl": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/passwordRules.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/formatDate.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/extractApiError.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/categoryVariant.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/newsletterService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/commentService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/authService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/articleService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/api.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/router/AppRouter.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/router/AdminRoute.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Perfil.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/NotFound.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Newsletter.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/News.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Home.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/Register.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/Login.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Article.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/About/index.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/main.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/contexts/AuthContext.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Modal.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Input.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Button.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Badge.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Avatar.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/Navbar.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/Footer.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ErrorFallback.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/assets/seek-white.svg?import", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/App.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/axios.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/@vite/client", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/@react-refresh", + "subItems": { + "type": "subitems", + "items": [] + } + } + ] + } + }, + "prioritize-lcp-image": { + "id": "prioritize-lcp-image", + "title": "Preload Largest Contentful Paint image", + "description": "If the LCP element is dynamically added to the page, you should preload the image in order to improve LCP. [Learn more about preloading LCP elements](https://web.dev/articles/optimize-lcp#optimize_when_the_resource_is_discovered).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 4 + }, + "csp-xss": { + "id": "csp-xss", + "title": "Ensure CSP is effective against XSS attacks", + "description": "A strong Content Security Policy (CSP) significantly reduces the risk of cross-site scripting (XSS) attacks. [Learn how to use a CSP to prevent XSS](https://developer.chrome.com/docs/lighthouse/best-practices/csp-xss/)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No CSP found in enforcement mode" + } + ] + } + }, + "has-hsts": { + "id": "has-hsts", + "title": "Use a strong HSTS policy", + "description": "Deployment of the HSTS header significantly reduces the risk of downgrading HTTP connections and eavesdropping attacks. A rollout in stages, starting with a low max-age is recommended. [Learn more about using a strong HSTS policy.](https://developer.chrome.com/docs/lighthouse/best-practices/has-hsts)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No HSTS header found" + } + ] + } + }, + "origin-isolation": { + "id": "origin-isolation", + "title": "Ensure proper origin isolation with COOP", + "description": "The Cross-Origin-Opener-Policy (COOP) can be used to isolate the top-level window from other documents such as pop-ups. [Learn more about deploying the COOP header.](https://web.dev/articles/why-coop-coep#coop)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "description": "No COOP header found", + "severity": "High" + } + ] + } + }, + "clickjacking-mitigation": { + "id": "clickjacking-mitigation", + "title": "Mitigate clickjacking with XFO or CSP", + "description": "The `X-Frame-Options` (XFO) header or the `frame-ancestors` directive in the `Content-Security-Policy` (CSP) header control where a page can be embedded. These can mitigate clickjacking attacks by blocking some or all sites from embedding the page. [Learn more about mitigating clickjacking](https://developer.chrome.com/docs/lighthouse/best-practices/clickjacking-mitigation).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No frame control policy found" + } + ] + } + }, + "trusted-types-xss": { + "id": "trusted-types-xss", + "title": "Mitigate DOM-based XSS with Trusted Types", + "description": "The `require-trusted-types-for` directive in the `Content-Security-Policy` (CSP) header instructs user agents to control the data passed to DOM XSS sink functions. [Learn more about mitigating DOM-based XSS with Trusted Types](https://developer.chrome.com/docs/lighthouse/best-practices/trusted-types-xss).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No `Content-Security-Policy` header with Trusted Types directive found" + } + ] + } + }, + "script-treemap-data": { + "id": "script-treemap-data", + "title": "Script Treemap Data", + "description": "Used for treemap app", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "treemap-data", + "nodes": [ + { + "name": "http://localhost:5173/", + "resourceBytes": 329, + "encodedBytes": 329, + "unusedBytes": 28, + "children": [ + { + "name": "(inline) window.addEvent…", + "resourceBytes": 168, + "unusedBytes": 0 + }, + { + "name": "(inline) import { inject…", + "resourceBytes": 161, + "unusedBytes": 28 + } + ] + }, + { + "name": "http://localhost:5173/@vite/client", + "resourceBytes": 209821, + "encodedBytes": 209821, + "unusedBytes": 22340, + "children": [ + { + "name": "client", + "resourceBytes": 39552, + "unusedBytes": 22340 + }, + { + "name": "(unmapped)", + "resourceBytes": 170269 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "resourceBytes": 3478, + "encodedBytes": 3478, + "unusedBytes": 272, + "children": [ + { + "name": "env.mjs", + "resourceBytes": 603, + "unusedBytes": 272 + }, + { + "name": "(unmapped)", + "resourceBytes": 2875 + } + ] + }, + { + "name": "http://localhost:5173/@react-refresh", + "resourceBytes": 111894, + "encodedBytes": 111894, + "unusedBytes": 6131, + "children": [ + { + "name": "@react-refresh", + "resourceBytes": 21291, + "unusedBytes": 6131 + }, + { + "name": "(unmapped)", + "resourceBytes": 90603 + } + ] + }, + { + "name": "http://localhost:5173/src/main.tsx", + "resourceBytes": 2040, + "encodedBytes": 2040, + "children": [ + { + "name": "main.tsx", + "resourceBytes": 751 + }, + { + "name": "(unmapped)", + "resourceBytes": 1289 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "resourceBytes": 38303, + "encodedBytes": 38303, + "unusedBytes": 17278, + "children": [ + { + "name": "../../react", + "resourceBytes": 35028, + "unusedBytes": 17278, + "children": [ + { + "name": "cjs/react.development.js", + "resourceBytes": 34982, + "unusedBytes": 17278 + }, + { + "name": "index.js", + "resourceBytes": 46 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 3275 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "resourceBytes": 7614, + "encodedBytes": 7614, + "children": [ + { + "name": "chunk-B-1-B7_t.js?v=696798f9", + "resourceBytes": 1407 + }, + { + "name": "(unmapped)", + "resourceBytes": 6207 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "resourceBytes": 820938, + "encodedBytes": 820982, + "unusedBytes": 310960, + "children": [ + { + "name": "../..", + "resourceBytes": 759489, + "unusedBytes": 310960, + "children": [ + { + "name": "scheduler", + "resourceBytes": 8551, + "unusedBytes": 1969, + "children": [ + { + "name": "cjs/scheduler.development.js", + "resourceBytes": 8501, + "unusedBytes": 1969 + }, + { + "name": "index.js", + "resourceBytes": 50 + } + ] + }, + { + "name": "react-dom", + "resourceBytes": 750938, + "unusedBytes": 308991, + "children": [ + { + "name": "cjs/react-dom-client.development.js", + "resourceBytes": 750881, + "unusedBytes": 308991 + }, + { + "name": "client.js", + "resourceBytes": 57 + } + ] + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 61449 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "resourceBytes": 14630, + "encodedBytes": 14630, + "unusedBytes": 11676, + "children": [ + { + "name": "../../react-dom", + "resourceBytes": 13284, + "unusedBytes": 11676, + "children": [ + { + "name": "cjs/react-dom.development.js", + "resourceBytes": 13234, + "unusedBytes": 11676 + }, + { + "name": "index.js", + "resourceBytes": 50 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 1346 + } + ] + }, + { + "name": "http://localhost:5173/src/styles/global.css", + "resourceBytes": 16411, + "encodedBytes": 16581, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/App.tsx", + "resourceBytes": 2239, + "encodedBytes": 2239, + "children": [ + { + "name": "App.tsx", + "resourceBytes": 353 + }, + { + "name": "(unmapped)", + "resourceBytes": 1886 + } + ] + }, + { + "name": "http://localhost:5173/src/router/AppRouter.tsx", + "resourceBytes": 21313, + "encodedBytes": 21319, + "unusedBytes": 502, + "children": [ + { + "name": "AppRouter.tsx", + "resourceBytes": 5421, + "unusedBytes": 502 + }, + { + "name": "(unmapped)", + "resourceBytes": 15892 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "resourceBytes": 398951, + "encodedBytes": 398955, + "unusedBytes": 322162, + "children": [ + { + "name": "../..", + "resourceBytes": 365753, + "unusedBytes": 322162, + "children": [ + { + "name": "react-router/dist/development", + "resourceBytes": 357318, + "unusedBytes": 314680, + "children": [ + { + "name": "chunk-4N6VE7H7.mjs", + "resourceBytes": 290362, + "unusedBytes": 248662 + }, + { + "name": "chunk-RJYABSBD.mjs", + "resourceBytes": 61391, + "unusedBytes": 60496 + }, + { + "name": "dom-export.mjs", + "resourceBytes": 5565, + "unusedBytes": 5522 + } + ] + }, + { + "name": "cookie/dist/index.js", + "resourceBytes": 4071, + "unusedBytes": 3354 + }, + { + "name": "set-cookie-parser/lib/set-cookie.js", + "resourceBytes": 4364, + "unusedBytes": 4128 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 33198 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "resourceBytes": 3150, + "encodedBytes": 3150, + "unusedBytes": 1711, + "children": [ + { + "name": "../../react-error-boundary/dist/react-error-boundary.js", + "resourceBytes": 2461, + "unusedBytes": 1711 + }, + { + "name": "(unmapped)", + "resourceBytes": 689 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ErrorFallback.tsx", + "resourceBytes": 9515, + "encodedBytes": 9524, + "unusedBytes": 1542, + "children": [ + { + "name": "ErrorFallback.tsx", + "resourceBytes": 1997, + "unusedBytes": 1542 + }, + { + "name": "(unmapped)", + "resourceBytes": 7518 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ErrorFallback.css", + "resourceBytes": 2833, + "encodedBytes": 2833, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "resourceBytes": 10573, + "encodedBytes": 10573, + "unusedBytes": 869, + "children": [ + { + "name": "../../react", + "resourceBytes": 9137, + "unusedBytes": 869, + "children": [ + { + "name": "cjs/react-jsx-dev-runtime.development.js", + "resourceBytes": 9075, + "unusedBytes": 869 + }, + { + "name": "jsx-dev-runtime.js", + "resourceBytes": 62 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 1436 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Home.tsx", + "resourceBytes": 21978, + "encodedBytes": 22007, + "unusedBytes": 228, + "children": [ + { + "name": "Home.tsx", + "resourceBytes": 4680, + "unusedBytes": 228 + }, + { + "name": "(unmapped)", + "resourceBytes": 17298 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "resourceBytes": 4500, + "encodedBytes": 4501, + "children": [ + { + "name": "PageLayout.tsx", + "resourceBytes": 836 + }, + { + "name": "(unmapped)", + "resourceBytes": 3664 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/Navbar.tsx", + "resourceBytes": 21435, + "encodedBytes": 21449, + "unusedBytes": 612, + "children": [ + { + "name": "Navbar.tsx", + "resourceBytes": 4831, + "unusedBytes": 612 + }, + { + "name": "(unmapped)", + "resourceBytes": 16604 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Button.tsx", + "resourceBytes": 3460, + "encodedBytes": 3460, + "children": [ + { + "name": "Button.tsx", + "resourceBytes": 552 + }, + { + "name": "(unmapped)", + "resourceBytes": 2908 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Button.css", + "resourceBytes": 1870, + "encodedBytes": 1870, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/contexts/AuthContext.tsx", + "resourceBytes": 11441, + "encodedBytes": 11454, + "unusedBytes": 347, + "children": [ + { + "name": "AuthContext.tsx", + "resourceBytes": 1556, + "unusedBytes": 347 + }, + { + "name": "(unmapped)", + "resourceBytes": 9885 + } + ] + }, + { + "name": "http://localhost:5173/src/services/authService.ts", + "resourceBytes": 5512, + "encodedBytes": 5520, + "unusedBytes": 742, + "children": [ + { + "name": "authService.ts", + "resourceBytes": 966, + "unusedBytes": 742 + }, + { + "name": "(unmapped)", + "resourceBytes": 4546 + } + ] + }, + { + "name": "http://localhost:5173/src/services/api.ts", + "resourceBytes": 6673, + "encodedBytes": 6785, + "unusedBytes": 152, + "children": [ + { + "name": "api.ts", + "resourceBytes": 946, + "unusedBytes": 152 + }, + { + "name": "(unmapped)", + "resourceBytes": 5727 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "resourceBytes": 107708, + "encodedBytes": 107708, + "children": [ + { + "name": "../../axios", + "resourceBytes": 78466, + "children": [ + { + "name": "lib", + "resourceBytes": 78262, + "children": [ + { + "name": "helpers", + "resourceBytes": 23165, + "children": [ + { + "name": "bind.js", + "resourceBytes": 96 + }, + { + "name": "parseHeaders.js", + "resourceBytes": 794, + "unusedBytes": 465 + }, + { + "name": "sanitizeHeaderValue.js", + "resourceBytes": 1263, + "unusedBytes": 43 + }, + { + "name": "toFormData.js", + "resourceBytes": 3780, + "unusedBytes": 583 + }, + { + "name": "AxiosURLSearchParams.js", + "resourceBytes": 746, + "unusedBytes": 227 + }, + { + "name": "buildURL.js", + "resourceBytes": 840 + }, + { + "name": "toURLEncodedForm.js", + "resourceBytes": 364, + "unusedBytes": 363 + }, + { + "name": "formDataToJSON.js", + "resourceBytes": 1371, + "unusedBytes": 1368 + }, + { + "name": "parseProtocol.js", + "resourceBytes": 117 + }, + { + "name": "speedometer.js", + "resourceBytes": 774, + "unusedBytes": 773 + }, + { + "name": "throttle.js", + "resourceBytes": 578, + "unusedBytes": 577 + }, + { + "name": "progressEventReducer.js", + "resourceBytes": 1063, + "unusedBytes": 980 + }, + { + "name": "isURLSameOrigin.js", + "resourceBytes": 400, + "unusedBytes": 10 + }, + { + "name": "cookies.js", + "resourceBytes": 1081, + "unusedBytes": 997 + }, + { + "name": "isAbsoluteURL.js", + "resourceBytes": 123 + }, + { + "name": "combineURLs.js", + "resourceBytes": 149 + }, + { + "name": "resolveConfig.js", + "resourceBytes": 1970, + "unusedBytes": 382 + }, + { + "name": "composeSignals.js", + "resourceBytes": 1044, + "unusedBytes": 1021 + }, + { + "name": "trackStream.js", + "resourceBytes": 1299, + "unusedBytes": 1222 + }, + { + "name": "estimateDataURLDecodedBytes.js", + "resourceBytes": 1616, + "unusedBytes": 1615 + }, + { + "name": "validator.js", + "resourceBytes": 1900, + "unusedBytes": 251 + }, + { + "name": "spread.js", + "resourceBytes": 97, + "unusedBytes": 96 + }, + { + "name": "isAxiosError.js", + "resourceBytes": 110, + "unusedBytes": 109 + }, + { + "name": "HttpStatusCode.js", + "resourceBytes": 1590 + } + ], + "unusedBytes": 11082 + }, + { + "name": "utils.js", + "resourceBytes": 11433, + "unusedBytes": 2731 + }, + { + "name": "core", + "resourceBytes": 21155, + "unusedBytes": 6123, + "children": [ + { + "name": "AxiosHeaders.js", + "resourceBytes": 6322, + "unusedBytes": 2075 + }, + { + "name": "AxiosError.js", + "resourceBytes": 3548, + "unusedBytes": 2299 + }, + { + "name": "InterceptorManager.js", + "resourceBytes": 508, + "unusedBytes": 112 + }, + { + "name": "transformData.js", + "resourceBytes": 368 + }, + { + "name": "settle.js", + "resourceBytes": 434, + "unusedBytes": 433 + }, + { + "name": "buildFullPath.js", + "resourceBytes": 246 + }, + { + "name": "mergeConfig.js", + "resourceBytes": 3020 + }, + { + "name": "dispatchRequest.js", + "resourceBytes": 1352, + "unusedBytes": 307 + }, + { + "name": "Axios.js", + "resourceBytes": 5357, + "unusedBytes": 897 + } + ] + }, + { + "name": "defaults", + "resourceBytes": 3475, + "children": [ + { + "name": "transitional.js", + "resourceBytes": 148 + }, + { + "name": "index.js", + "resourceBytes": 3327, + "unusedBytes": 967 + } + ], + "unusedBytes": 967 + }, + { + "name": "platform", + "resourceBytes": 918, + "children": [ + { + "name": "browser", + "resourceBytes": 327, + "children": [ + { + "name": "index.js", + "resourceBytes": 153 + }, + { + "name": "classes", + "resourceBytes": 174, + "children": [ + { + "name": "URLSearchParams.js", + "resourceBytes": 81 + }, + { + "name": "FormData.js", + "resourceBytes": 51 + }, + { + "name": "Blob.js", + "resourceBytes": 42 + } + ] + } + ] + }, + { + "name": "common/utils.js", + "resourceBytes": 526 + }, + { + "name": "index.js", + "resourceBytes": 65 + } + ] + }, + { + "name": "cancel", + "resourceBytes": 1914, + "children": [ + { + "name": "isCancel.js", + "resourceBytes": 69 + }, + { + "name": "CanceledError.js", + "resourceBytes": 240, + "unusedBytes": 185 + }, + { + "name": "CancelToken.js", + "resourceBytes": 1605, + "unusedBytes": 1547 + } + ], + "unusedBytes": 1732 + }, + { + "name": "adapters", + "resourceBytes": 15020, + "unusedBytes": 8327, + "children": [ + { + "name": "xhr.js", + "resourceBytes": 4053, + "unusedBytes": 1092 + }, + { + "name": "fetch.js", + "resourceBytes": 9343, + "unusedBytes": 7083 + }, + { + "name": "adapters.js", + "resourceBytes": 1624, + "unusedBytes": 152 + } + ] + }, + { + "name": "env/data.js", + "resourceBytes": 26 + }, + { + "name": "axios.js", + "resourceBytes": 1156, + "unusedBytes": 144 + } + ], + "unusedBytes": 31106 + }, + { + "name": "index.js", + "resourceBytes": 204 + } + ], + "unusedBytes": 31106 + }, + { + "name": "(unmapped)", + "resourceBytes": 29242 + } + ], + "unusedBytes": 31106 + }, + { + "name": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "resourceBytes": 19771, + "encodedBytes": 19799, + "unusedBytes": 3800, + "children": [ + { + "name": "NavbarUserMenu.tsx", + "resourceBytes": 4099, + "unusedBytes": 3800 + }, + { + "name": "(unmapped)", + "resourceBytes": 15672 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Avatar.tsx", + "resourceBytes": 4292, + "encodedBytes": 4302, + "unusedBytes": 326, + "children": [ + { + "name": "Avatar.tsx", + "resourceBytes": 374, + "unusedBytes": 326 + }, + { + "name": "(unmapped)", + "resourceBytes": 3918 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Avatar.css", + "resourceBytes": 807, + "encodedBytes": 812, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "resourceBytes": 401, + "encodedBytes": 401, + "children": [ + { + "name": "interpop-logo.svg?import", + "resourceBytes": 47 + }, + { + "name": "(unmapped)", + "resourceBytes": 354 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/Navbar.css", + "resourceBytes": 8274, + "encodedBytes": 8296, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/layout/Footer.tsx", + "resourceBytes": 21395, + "encodedBytes": 21423, + "unusedBytes": 352, + "children": [ + { + "name": "Footer.tsx", + "resourceBytes": 4938, + "unusedBytes": 352 + }, + { + "name": "(unmapped)", + "resourceBytes": 16457 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "resourceBytes": 5048, + "encodedBytes": 5052, + "children": [ + { + "name": "DevelopedBy.tsx", + "resourceBytes": 888 + }, + { + "name": "(unmapped)", + "resourceBytes": 4160 + } + ] + }, + { + "name": "http://localhost:5173/src/assets/seek-white.svg?import", + "resourceBytes": 10042, + "encodedBytes": 10042, + "children": [ + { + "name": "seek-white.svg?import", + "resourceBytes": 1736 + }, + { + "name": "(unmapped)", + "resourceBytes": 8306 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "resourceBytes": 1240, + "encodedBytes": 1242, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/services/newsletterService.ts", + "resourceBytes": 1073, + "encodedBytes": 1073, + "unusedBytes": 128, + "children": [ + { + "name": "newsletterService.ts", + "resourceBytes": 260, + "unusedBytes": 128 + }, + { + "name": "(unmapped)", + "resourceBytes": 813 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/Footer.css", + "resourceBytes": 4810, + "encodedBytes": 4830, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/layout/PageLayout.css", + "resourceBytes": 1401, + "encodedBytes": 1499, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "resourceBytes": 12334, + "encodedBytes": 12340, + "unusedBytes": 1834, + "children": [ + { + "name": "NewsCard.tsx", + "resourceBytes": 2910, + "unusedBytes": 1834 + }, + { + "name": "(unmapped)", + "resourceBytes": 9424 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Badge.tsx", + "resourceBytes": 2983, + "encodedBytes": 2983, + "unusedBytes": 198, + "children": [ + { + "name": "Badge.tsx", + "resourceBytes": 462, + "unusedBytes": 198 + }, + { + "name": "(unmapped)", + "resourceBytes": 2521 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Badge.css", + "resourceBytes": 1804, + "encodedBytes": 1813, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/utils/categoryVariant.ts", + "resourceBytes": 2270, + "encodedBytes": 2272, + "unusedBytes": 143, + "children": [ + { + "name": "categoryVariant.ts", + "resourceBytes": 345, + "unusedBytes": 143 + }, + { + "name": "(unmapped)", + "resourceBytes": 1925 + } + ] + }, + { + "name": "http://localhost:5173/src/utils/formatDate.ts", + "resourceBytes": 4815, + "encodedBytes": 4823, + "unusedBytes": 707, + "children": [ + { + "name": "formatDate.ts", + "resourceBytes": 732, + "unusedBytes": 707 + }, + { + "name": "(unmapped)", + "resourceBytes": 4083 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCard.css", + "resourceBytes": 6585, + "encodedBytes": 6611, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "resourceBytes": 35307, + "encodedBytes": 35586, + "unusedBytes": 6216, + "children": [ + { + "name": "NewsCarousel.tsx", + "resourceBytes": 6862, + "unusedBytes": 6216 + }, + { + "name": "(unmapped)", + "resourceBytes": 28445 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "resourceBytes": 3679, + "encodedBytes": 3707, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/services/articleService.ts", + "resourceBytes": 9433, + "encodedBytes": 9548, + "unusedBytes": 1143, + "children": [ + { + "name": "articleService.ts", + "resourceBytes": 1440, + "unusedBytes": 1143 + }, + { + "name": "(unmapped)", + "resourceBytes": 7993 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Home.css", + "resourceBytes": 9077, + "encodedBytes": 9192, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/News.tsx", + "resourceBytes": 30992, + "encodedBytes": 31011, + "unusedBytes": 6157, + "children": [ + { + "name": "News.tsx", + "resourceBytes": 7132, + "unusedBytes": 6157 + }, + { + "name": "(unmapped)", + "resourceBytes": 23860 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/News.css", + "resourceBytes": 1590, + "encodedBytes": 1592, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Newsletter.tsx", + "resourceBytes": 18778, + "encodedBytes": 18826, + "unusedBytes": 4009, + "children": [ + { + "name": "Newsletter.tsx", + "resourceBytes": 4663, + "unusedBytes": 4009 + }, + { + "name": "(unmapped)", + "resourceBytes": 14115 + } + ] + }, + { + "name": "http://localhost:5173/src/utils/extractApiError.ts", + "resourceBytes": 6805, + "encodedBytes": 6820, + "unusedBytes": 998, + "children": [ + { + "name": "extractApiError.ts", + "resourceBytes": 1006, + "unusedBytes": 998 + }, + { + "name": "(unmapped)", + "resourceBytes": 5799 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Newsletter.css", + "resourceBytes": 4683, + "encodedBytes": 4707, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/About/index.tsx", + "resourceBytes": 5492, + "encodedBytes": 5500, + "unusedBytes": 649, + "children": [ + { + "name": "index.tsx", + "resourceBytes": 1043, + "unusedBytes": 649 + }, + { + "name": "(unmapped)", + "resourceBytes": 4449 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "resourceBytes": 15372, + "encodedBytes": 15442, + "unusedBytes": 3316, + "children": [ + { + "name": "AboutContent.tsx", + "resourceBytes": 3404, + "unusedBytes": 3316 + }, + { + "name": "(unmapped)", + "resourceBytes": 11968 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/About/About.css", + "resourceBytes": 3313, + "encodedBytes": 3627, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "resourceBytes": 4475, + "encodedBytes": 4475, + "unusedBytes": 586, + "children": [ + { + "name": "Termos.tsx", + "resourceBytes": 944, + "unusedBytes": 586 + }, + { + "name": "(unmapped)", + "resourceBytes": 3531 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "resourceBytes": 74854, + "encodedBytes": 75419, + "unusedBytes": 22267, + "children": [ + { + "name": "LegalContent.tsx", + "resourceBytes": 22403, + "unusedBytes": 22267 + }, + { + "name": "(unmapped)", + "resourceBytes": 52451 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Legal/Legal.css", + "resourceBytes": 3820, + "encodedBytes": 3822, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "resourceBytes": 4571, + "encodedBytes": 4572, + "unusedBytes": 606, + "children": [ + { + "name": "Privacidade.tsx", + "resourceBytes": 969, + "unusedBytes": 606 + }, + { + "name": "(unmapped)", + "resourceBytes": 3602 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Article.tsx", + "resourceBytes": 34893, + "encodedBytes": 34910, + "unusedBytes": 7195, + "children": [ + { + "name": "Article.tsx", + "resourceBytes": 8566, + "unusedBytes": 7195 + }, + { + "name": "(unmapped)", + "resourceBytes": 26327 + } + ] + }, + { + "name": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "resourceBytes": 15628, + "encodedBytes": 15628, + "unusedBytes": 1091, + "children": [ + { + "name": "ArticleShareBar.tsx", + "resourceBytes": 3998, + "unusedBytes": 1091 + }, + { + "name": "(unmapped)", + "resourceBytes": 11630 + } + ] + }, + { + "name": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "resourceBytes": 11195, + "encodedBytes": 11230, + "unusedBytes": 1800, + "children": [ + { + "name": "ArticleAdminActions.tsx", + "resourceBytes": 2048, + "unusedBytes": 1800 + }, + { + "name": "(unmapped)", + "resourceBytes": 9147 + } + ] + }, + { + "name": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "resourceBytes": 26250, + "encodedBytes": 26307, + "unusedBytes": 4822, + "children": [ + { + "name": "ArticleComments.tsx", + "resourceBytes": 5244, + "unusedBytes": 4822 + }, + { + "name": "(unmapped)", + "resourceBytes": 21006 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "resourceBytes": 24305, + "encodedBytes": 24326, + "unusedBytes": 4983, + "children": [ + { + "name": "CommentItem.tsx", + "resourceBytes": 5814, + "unusedBytes": 4983 + }, + { + "name": "(unmapped)", + "resourceBytes": 18491 + } + ] + }, + { + "name": "http://localhost:5173/src/services/commentService.ts", + "resourceBytes": 2593, + "encodedBytes": 2593, + "unusedBytes": 292, + "children": [ + { + "name": "commentService.ts", + "resourceBytes": 429, + "unusedBytes": 292 + }, + { + "name": "(unmapped)", + "resourceBytes": 2164 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/CommentItem.css", + "resourceBytes": 3973, + "encodedBytes": 3973, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "resourceBytes": 6032, + "encodedBytes": 6042, + "unusedBytes": 963, + "children": [ + { + "name": "renderArticleBody.tsx", + "resourceBytes": 971, + "unusedBytes": 963 + }, + { + "name": "(unmapped)", + "resourceBytes": 5061 + } + ] + }, + { + "name": "http://localhost:5173/src/styles/article-body.css", + "resourceBytes": 2183, + "encodedBytes": 2187, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Article.css", + "resourceBytes": 12725, + "encodedBytes": 12996, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/Login.tsx", + "resourceBytes": 12785, + "encodedBytes": 12805, + "unusedBytes": 2362, + "children": [ + { + "name": "Login.tsx", + "resourceBytes": 3151, + "unusedBytes": 2362 + }, + { + "name": "(unmapped)", + "resourceBytes": 9634 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "resourceBytes": 19215, + "encodedBytes": 19226, + "unusedBytes": 3527, + "children": [ + { + "name": "AuthLayout.tsx", + "resourceBytes": 4401, + "unusedBytes": 3527 + }, + { + "name": "(unmapped)", + "resourceBytes": 14814 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Modal.tsx", + "resourceBytes": 13421, + "encodedBytes": 13434, + "unusedBytes": 2161, + "children": [ + { + "name": "Modal.tsx", + "resourceBytes": 2915, + "unusedBytes": 2161 + }, + { + "name": "(unmapped)", + "resourceBytes": 10506 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Modal.css", + "resourceBytes": 1954, + "encodedBytes": 1954, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/layout/AuthLayout.css", + "resourceBytes": 4408, + "encodedBytes": 4435, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/ui/Input.tsx", + "resourceBytes": 4149, + "encodedBytes": 4149, + "unusedBytes": 486, + "children": [ + { + "name": "Input.tsx", + "resourceBytes": 750, + "unusedBytes": 486 + }, + { + "name": "(unmapped)", + "resourceBytes": 3399 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Input.css", + "resourceBytes": 1478, + "encodedBytes": 1478, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/Auth.css", + "resourceBytes": 2958, + "encodedBytes": 2966, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/Register.tsx", + "resourceBytes": 27647, + "encodedBytes": 27667, + "unusedBytes": 5430, + "children": [ + { + "name": "Register.tsx", + "resourceBytes": 6616, + "unusedBytes": 5430 + }, + { + "name": "(unmapped)", + "resourceBytes": 21031 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "resourceBytes": 6578, + "encodedBytes": 6600, + "unusedBytes": 775, + "children": [ + { + "name": "PasswordChecklist.tsx", + "resourceBytes": 1125, + "unusedBytes": 775 + }, + { + "name": "(unmapped)", + "resourceBytes": 5453 + } + ] + }, + { + "name": "http://localhost:5173/src/utils/passwordRules.ts", + "resourceBytes": 3112, + "encodedBytes": 3120, + "unusedBytes": 207, + "children": [ + { + "name": "passwordRules.ts", + "resourceBytes": 608, + "unusedBytes": 207 + }, + { + "name": "(unmapped)", + "resourceBytes": 2504 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "resourceBytes": 1682, + "encodedBytes": 1708, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "resourceBytes": 12527, + "encodedBytes": 12541, + "unusedBytes": 2385, + "children": [ + { + "name": "ForgotPassword.tsx", + "resourceBytes": 3108, + "unusedBytes": 2385 + }, + { + "name": "(unmapped)", + "resourceBytes": 9419 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "resourceBytes": 17916, + "encodedBytes": 17930, + "unusedBytes": 3630, + "children": [ + { + "name": "ResetPassword.tsx", + "resourceBytes": 4518, + "unusedBytes": 3630 + }, + { + "name": "(unmapped)", + "resourceBytes": 13398 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Perfil.tsx", + "resourceBytes": 55208, + "encodedBytes": 55540, + "unusedBytes": 11009, + "children": [ + { + "name": "Perfil.tsx", + "resourceBytes": 11720, + "unusedBytes": 11009 + }, + { + "name": "(unmapped)", + "resourceBytes": 43488 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Perfil.css", + "resourceBytes": 4623, + "encodedBytes": 4702, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/NotFound.tsx", + "resourceBytes": 10252, + "encodedBytes": 10287, + "unusedBytes": 1684, + "children": [ + { + "name": "NotFound.tsx", + "resourceBytes": 1972, + "unusedBytes": 1684 + }, + { + "name": "(unmapped)", + "resourceBytes": 8280 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/NotFound.css", + "resourceBytes": 1938, + "encodedBytes": 1938, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "resourceBytes": 12421, + "encodedBytes": 12442, + "unusedBytes": 2049, + "children": [ + { + "name": "Unsubscribe.tsx", + "resourceBytes": 2811, + "unusedBytes": 2049 + }, + { + "name": "(unmapped)", + "resourceBytes": 9610 + } + ] + }, + { + "name": "http://localhost:5173/src/router/AdminRoute.tsx", + "resourceBytes": 5913, + "encodedBytes": 5928, + "unusedBytes": 534, + "children": [ + { + "name": "AdminRoute.tsx", + "resourceBytes": 968, + "unusedBytes": 534 + }, + { + "name": "(unmapped)", + "resourceBytes": 4945 + } + ] + }, + { + "name": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "resourceBytes": 10074, + "encodedBytes": 10086, + "unusedBytes": 234, + "children": [ + { + "name": "ScrollToHashOrTop.tsx", + "resourceBytes": 1530, + "unusedBytes": 234 + }, + { + "name": "(unmapped)", + "resourceBytes": 8544 + } + ] + } + ] + } + }, + "accesskeys": { + "id": "accesskeys", + "title": "`[accesskey]` values are unique", + "description": "Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more about access keys](https://dequeuniversity.com/rules/axe/4.10/accesskeys).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-allowed-attr": { + "id": "aria-allowed-attr", + "title": "`[aria-*]` attributes match their roles", + "description": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn how to match ARIA attributes to their roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-allowed-role": { + "id": "aria-allowed-role", + "title": "Uses ARIA roles only on compatible elements", + "description": "Many HTML elements can only be assigned certain ARIA roles. Using ARIA roles where they are not allowed can interfere with the accessibility of the web page. [Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-command-name": { + "id": "aria-command-name", + "title": "`button`, `link`, and `menuitem` elements have accessible names", + "description": "When an element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to make command elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-command-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-conditional-attr": { + "id": "aria-conditional-attr", + "title": "ARIA attributes are used as specified for the element's role", + "description": "Some ARIA attributes are only allowed on an element under certain conditions. [Learn more about conditional ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-conditional-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-deprecated-role": { + "id": "aria-deprecated-role", + "title": "Deprecated ARIA roles were not used", + "description": "Deprecated ARIA roles may not be processed correctly by assistive technology. [Learn more about deprecated ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-deprecated-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-dialog-name": { + "id": "aria-dialog-name", + "title": "Elements with `role=\"dialog\"` or `role=\"alertdialog\"` have accessible names.", + "description": "ARIA dialog elements without accessible names may prevent screen readers users from discerning the purpose of these elements. [Learn how to make ARIA dialog elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-dialog-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-hidden-body": { + "id": "aria-hidden-body", + "title": "`[aria-hidden=\"true\"]` is not present on the document ``", + "description": "Assistive technologies, like screen readers, work inconsistently when `aria-hidden=\"true\"` is set on the document ``. [Learn how `aria-hidden` affects the document body](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-body).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-hidden-focus": { + "id": "aria-hidden-focus", + "title": "`[aria-hidden=\"true\"]` elements do not contain focusable descendents", + "description": "Focusable descendents within an `[aria-hidden=\"true\"]` element prevent those interactive elements from being available to users of assistive technologies like screen readers. [Learn how `aria-hidden` affects focusable elements](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-input-field-name": { + "id": "aria-input-field-name", + "title": "ARIA input fields have accessible names", + "description": "When an input field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about input field labels](https://dequeuniversity.com/rules/axe/4.10/aria-input-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-meter-name": { + "id": "aria-meter-name", + "title": "ARIA `meter` elements have accessible names", + "description": "When a meter element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `meter` elements](https://dequeuniversity.com/rules/axe/4.10/aria-meter-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-progressbar-name": { + "id": "aria-progressbar-name", + "title": "ARIA `progressbar` elements have accessible names", + "description": "When a `progressbar` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to label `progressbar` elements](https://dequeuniversity.com/rules/axe/4.10/aria-progressbar-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-prohibited-attr": { + "id": "aria-prohibited-attr", + "title": "Elements use only permitted ARIA attributes", + "description": "Using ARIA attributes in roles where they are prohibited can mean that important information is not communicated to users of assistive technologies. [Learn more about prohibited ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-prohibited-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-attr": { + "id": "aria-required-attr", + "title": "`[role]`s have all required `[aria-*]` attributes", + "description": "Some ARIA roles have required attributes that describe the state of the element to screen readers. [Learn more about roles and required attributes](https://dequeuniversity.com/rules/axe/4.10/aria-required-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-children": { + "id": "aria-required-children", + "title": "Elements with an ARIA `[role]` that require children to contain a specific `[role]` have all required children.", + "description": "Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions. [Learn more about roles and required children elements](https://dequeuniversity.com/rules/axe/4.10/aria-required-children).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-required-parent": { + "id": "aria-required-parent", + "title": "`[role]`s are contained by their required parent element", + "description": "Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions. [Learn more about ARIA roles and required parent element](https://dequeuniversity.com/rules/axe/4.10/aria-required-parent).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-roles": { + "id": "aria-roles", + "title": "`[role]` values are valid", + "description": "ARIA roles must have valid values in order to perform their intended accessibility functions. [Learn more about valid ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-roles).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-text": { + "id": "aria-text", + "title": "Elements with the `role=text` attribute do not have focusable descendents.", + "description": "Adding `role=text` around a text node split by markup enables VoiceOver to treat it as one phrase, but the element's focusable descendents will not be announced. [Learn more about the `role=text` attribute](https://dequeuniversity.com/rules/axe/4.10/aria-text).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-toggle-field-name": { + "id": "aria-toggle-field-name", + "title": "ARIA toggle fields have accessible names", + "description": "When a toggle field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about toggle fields](https://dequeuniversity.com/rules/axe/4.10/aria-toggle-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-tooltip-name": { + "id": "aria-tooltip-name", + "title": "ARIA `tooltip` elements have accessible names", + "description": "When a tooltip element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `tooltip` elements](https://dequeuniversity.com/rules/axe/4.10/aria-tooltip-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-treeitem-name": { + "id": "aria-treeitem-name", + "title": "ARIA `treeitem` elements have accessible names", + "description": "When a `treeitem` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about labeling `treeitem` elements](https://dequeuniversity.com/rules/axe/4.10/aria-treeitem-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-valid-attr-value": { + "id": "aria-valid-attr-value", + "title": "`[aria-*]` attributes have valid values", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more about valid values for ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr-value).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-valid-attr": { + "id": "aria-valid-attr", + "title": "`[aria-*]` attributes are valid and not misspelled", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more about valid ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "button-name": { + "id": "button-name", + "title": "Buttons have an accessible name", + "description": "When a button doesn't have an accessible name, screen readers announce it as \"button\", making it unusable for users who rely on screen readers. [Learn how to make buttons more accessible](https://dequeuniversity.com/rules/axe/4.10/button-name).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "bypass": { + "id": "bypass", + "title": "The page contains a heading, skip link, or landmark region", + "description": "Adding ways to bypass repetitive content lets keyboard users navigate the page more efficiently. [Learn more about bypass blocks](https://dequeuniversity.com/rules/axe/4.10/bypass).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "color-contrast": { + "id": "color-contrast", + "title": "Background and foreground colors have a sufficient contrast ratio", + "description": "Low-contrast text is difficult or impossible for many users to read. [Learn how to provide sufficient color contrast](https://dequeuniversity.com/rules/axe/4.10/color-contrast).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "definition-list": { + "id": "definition-list", + "title": "`
`'s contain only properly-ordered `
` and `
` groups, `", + "message": "Syntax not understood" + }, + { + "index": "9", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "11", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "12", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "15", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "16", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "17", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "18", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "19", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "23", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "24", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "25", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "29", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "30", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "31", + "line": " Interpop — Soft Power & Cultura Pop", + "message": "Syntax not understood" + }, + { + "index": "32", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "33", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "34", + "line": "
", + "message": "Syntax not understood" + }, + { + "index": "35", + "line": "
", + "message": "Syntax not understood" + }, + { + "index": "36", + "line": "
", + "message": "Syntax not understood" + }, + { + "index": "37", + "line": "
", + "message": "Syntax not understood" + }, + { + "index": "38", + "line": "
", + "message": "Syntax not understood" + }, + { + "index": "39", + "line": "
", + "message": "Syntax not understood" + }, + { + "index": "40", + "line": "
", + "message": "Syntax not understood" + }, + { + "index": "41", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "42", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "43", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "46", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "47", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "53", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "54", + "line": "", + "message": "Syntax not understood" + } + ] + } + }, + "hreflang": { + "id": "hreflang", + "title": "Document has a valid `hreflang`", + "description": "hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more about `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + }, + "label": "" + } + ], + "items": [] + } + }, + "canonical": { + "id": "canonical", + "title": "Document has a valid `rel=canonical`", + "description": "Canonical links suggest which URL to show in search results. [Learn more about canonical links](https://developer.chrome.com/docs/lighthouse/seo/canonical/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "structured-data": { + "id": "structured-data", + "title": "Structured data is valid", + "description": "Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more about Structured Data](https://developer.chrome.com/docs/lighthouse/seo/structured-data/).", + "score": null, + "scoreDisplayMode": "manual" + }, + "bf-cache": { + "id": "bf-cache", + "title": "Page prevented back/forward cache restoration", + "description": "Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://developer.chrome.com/docs/lighthouse/performance/bf-cache/)", + "score": 0, + "scoreDisplayMode": "binary", + "displayValue": "1 failure reason", + "details": { + "type": "table", + "headings": [ + { + "key": "reason", + "valueType": "text", + "subItemsHeading": { + "key": "frameUrl", + "valueType": "url" + }, + "label": "Failure reason" + }, + { + "key": "failureType", + "valueType": "text", + "label": "Failure type" + } + ], + "items": [ + { + "reason": "Pages with WebSocket cannot enter back/forward cache.", + "failureType": "Pending browser support", + "subItems": { + "type": "subitems", + "items": [ + { + "frameUrl": "http://localhost:5173/" + } + ] + }, + "protocolReason": "WebSocket" + } + ] + }, + "guidanceLevel": 4 + }, + "cache-insight": { + "id": "cache-insight", + "title": "Use efficient cache lifetimes", + "description": "A long cache lifetime can speed up repeat visits to your page. [Learn more](https://web.dev/uses-long-cache-ttl/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Request" + }, + { + "key": "cacheLifetimeMs", + "valueType": "ms", + "label": "Cache TTL", + "displayUnit": "duration" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size", + "displayUnit": "kb", + "granularity": 1 + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["uses-long-cache-ttl"] + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "text", + "value": "Total" + }, + "score": 0.15287 + }, + { + "node": { + "type": "node", + "lhId": "page-1-SECTION", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION", + "selector": "div#root > div.page-layout > main#main > section.home-news", + "boundingRect": { + "top": 539, + "bottom": 910, + "left": 0, + "right": 1335, + "width": 1335, + "height": 372 + }, + "snippet": "
", + "nodeLabel": "Últimas Notícias\nVer todas →\n\nNenhum artigo disponível no momento.\n\nVer todas a…" + }, + "score": 0.145676 + }, + { + "node": { + "type": "node", + "lhId": "page-4-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION,0,DIV,2,DIV", + "selector": "main#main > section.home-news > div.container > div.home-load-more", + "boundingRect": { + "top": 798, + "bottom": 846, + "left": 92, + "right": 1244, + "width": 1152, + "height": 48 + }, + "snippet": "
", + "nodeLabel": "Ver todas as notícias" + }, + "score": 0.007194, + "subItems": { + "type": "subitems", + "items": [ + { + "extra": { + "type": "node", + "lhId": "page-6-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1242, + "bottom": 1282, + "left": 92, + "right": 229, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + }, + "cause": "Unsized image element" + } + ] + } + } + ] + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": [ + "layout-shifts", + "non-composited-animations", + "unsized-images" + ] + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": 0.5, + "scoreDisplayMode": "metricSavings", + "displayValue": "Est savings of 2 KiB", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "checklist", + "items": { + "noRedirects": { + "label": "Avoids redirects", + "value": true + }, + "serverResponseIsFast": { + "label": "Server responds quickly (observed 9 ms)", + "value": true + }, + "usesCompression": { + "label": "No compression applied", + "value": false + } + }, + "debugData": { + "type": "debugdata", + "redirectDuration": 0, + "serverResponseTime": 9, + "uncompressedResponseBytes": 1561, + "wastedBytes": 1561 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "redirects", + "server-response-time", + "uses-text-compression" + ] + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 105 + } + }, + { + "statistic": "Most children", + "node": { + "type": "node", + "lhId": "page-8-UL", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,0,DIV,1,NAV,0,DIV,1,UL", + "selector": "div.footer__top > nav.footer__links > div.footer__col > ul", + "boundingRect": { + "top": 994, + "bottom": 1169, + "left": 609, + "right": 799, + "width": 190, + "height": 176 + }, + "snippet": "
    ", + "nodeLabel": "Música\nModa\nCinema\nLiteratura\nCultura Digital" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 5 + } + }, + { + "statistic": "DOM depth", + "node": { + "type": "node", + "lhId": "page-9-EM", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1,1,EM", + "selector": "div.home-hero__grid > div.home-hero__text > h1#hero-manifesto > em", + "boundingRect": { + "top": 156, + "bottom": 196, + "left": 131, + "right": 263, + "width": 133, + "height": 40 + }, + "snippet": "", + "nodeLabel": "Interpop" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 10 + } + } + ], + "debugData": { + "type": "debugdata", + "totalElements": 105, + "maxChildren": 5, + "maxDepth": 10 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["dom-size"] + }, + "duplicated-javascript-insight": { + "id": "duplicated-javascript-insight", + "title": "Duplicated JavaScript", + "description": "Remove large, duplicate JavaScript modules from bundles to reduce unnecessary bytes consumed by network activity.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "url", + "valueType": "url" + }, + "label": "Source" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "subItemsHeading": { + "key": "sourceTransferBytes" + }, + "granularity": 10, + "label": "Duplicated bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2, + "replacesAudits": ["duplicated-javascript"] + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["font-display"] + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "A forced reflow occurs when JavaScript queries geometric properties (such as offsetWidth) after styles have been invalidated by a change to the DOM state. This can result in poor performance. Learn more about [forced reflows](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and possible mitigations.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "reflowTime", + "valueType": "ms", + "granularity": 1, + "label": "Total reflow time" + } + ], + "items": [] + } + ] + }, + "guidanceLevel": 3 + }, + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + } + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Resource Size" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Est Savings", + "subItemsHeading": { + "key": "wastedBytes", + "valueType": "bytes" + } + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "modern-image-formats", + "uses-optimized-images", + "efficient-animated-content", + "uses-responsive-images" + ] + }, + "inp-breakdown-insight": { + "id": "inp-breakdown-insight", + "title": "INP breakdown", + "description": "Start investigating with the longest subpart. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["work-during-interaction"] + }, + "lcp-breakdown-insight": { + "id": "lcp-breakdown-insight", + "title": "LCP breakdown", + "description": "Each [subpart has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Subpart" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "subpart": "timeToFirstByte", + "label": "Time to first byte", + "duration": 14.809 + }, + { + "subpart": "elementRenderDelay", + "label": "Element render delay", + "duration": 606.988 + } + ] + }, + { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 152, + "bottom": 396, + "left": 92, + "right": 686, + "width": 595, + "height": 244 + }, + "snippet": "

    ", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["largest-contentful-paint-element"] + }, + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["prioritize-lcp-image", "lcp-lazy-loaded"] + }, + "legacy-javascript-insight": { + "id": "legacy-javascript-insight", + "title": "Legacy JavaScript", + "description": "Polyfills and transforms enable older browsers to use new JavaScript features. However, many aren't necessary for modern browsers. Consider modifying your JavaScript build process to not transpile [Baseline](https://web.dev/articles/baseline-and-polyfills) features, unless you know you must support older browsers. [Learn why most sites can deploy ES6+ code without transpiling](https://philipwalton.com/articles/the-state-of-es5-on-the-web/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "subItemsHeading": { + "key": "location", + "valueType": "source-location" + }, + "label": "URL" + }, + { + "key": null, + "valueType": "code", + "subItemsHeading": { + "key": "signal" + }, + "label": "" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Wasted bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2 + }, + "modern-http-insight": { + "id": "modern-http-insight", + "title": "Modern HTTP", + "description": "HTTP/2 and HTTP/3 offer many benefits over HTTP/1.1, such as multiplexing. [Learn more about using modern HTTP](https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "network-dependency-tree-insight": { + "id": "network-dependency-tree-insight", + "title": "Network dependency tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "list-section", + "value": { + "type": "network-tree", + "chains": { + "96CA62C53748FA62EC5B629E5F0ECB6F": { + "url": "http://localhost:5173/", + "navStartToEndTime": 35, + "transferSize": 2584, + "isLongest": true, + "children": { + "180065.3": { + "url": "http://localhost:5173/src/main.tsx", + "navStartToEndTime": 37, + "transferSize": 2330, + "isLongest": true, + "children": { + "180065.13": { + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "navStartToEndTime": 69, + "transferSize": 11746, + "isLongest": true, + "children": { + "180065.16": { + "url": "http://localhost:5173/src/services/authService.ts", + "navStartToEndTime": 87, + "transferSize": 5810, + "isLongest": true, + "children": { + "180065.18": { + "url": "http://localhost:5173/src/services/api.ts", + "navStartToEndTime": 107, + "transferSize": 7075, + "isLongest": true, + "children": { + "180065.20": { + "url": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "navStartToEndTime": 117, + "transferSize": 108020, + "isLongest": true, + "children": { + "180065.128": { + "url": "http://localhost:8000/api/v1/auth/me/", + "navStartToEndTime": 644, + "transferSize": 0, + "isLongest": true, + "children": {} + }, + "180065.127": { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "navStartToEndTime": 643, + "transferSize": 0, + "children": {} + }, + "180065.126": { + "url": "http://localhost:8000/api/v1/auth/me/", + "navStartToEndTime": 643, + "transferSize": 0, + "children": {} + }, + "180065.125": { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "navStartToEndTime": 642, + "transferSize": 0, + "children": {} + } + } + } + } + } + } + } + } + }, + "180065.12": { + "url": "http://localhost:5173/src/App.tsx", + "navStartToEndTime": 110, + "transferSize": 2529, + "children": { + "180065.22": { + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "navStartToEndTime": 118, + "transferSize": 21611, + "children": { + "180065.33": { + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "navStartToEndTime": 158, + "transferSize": 13097, + "children": { + "180065.68": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "navStartToEndTime": 223, + "transferSize": 19518, + "children": { + "180065.87": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "navStartToEndTime": 244, + "transferSize": 5342, + "children": { + "180065.97": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "navStartToEndTime": 254, + "transferSize": 1532, + "children": {} + }, + "180065.96": { + "url": "http://localhost:5173/src/assets/seek-white.svg?import", + "navStartToEndTime": 252, + "transferSize": 10333, + "children": {} + } + } + }, + "180065.88": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.css", + "navStartToEndTime": 245, + "transferSize": 4726, + "children": {} + } + } + }, + "180065.69": { + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "navStartToEndTime": 224, + "transferSize": 4439, + "children": { + "180065.89": { + "url": "http://localhost:5173/src/components/ui/Input.css", + "navStartToEndTime": 243, + "transferSize": 1768, + "children": {} + } + } + }, + "180065.70": { + "url": "http://localhost:5173/src/pages/Auth/Auth.css", + "navStartToEndTime": 222, + "transferSize": 3256, + "children": {} + } + } + }, + "180065.28": { + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "navStartToEndTime": 151, + "transferSize": 19118, + "children": { + "180065.43": { + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "navStartToEndTime": 191, + "transferSize": 4791, + "children": { + "180065.78": { + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "navStartToEndTime": 234, + "transferSize": 21715, + "children": { + "180065.94": { + "url": "http://localhost:5173/src/components/layout/Footer.css", + "navStartToEndTime": 252, + "transferSize": 5121, + "children": {} + } + } + }, + "180065.77": { + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "navStartToEndTime": 233, + "transferSize": 21741, + "children": { + "180065.93": { + "url": "http://localhost:5173/src/components/layout/Navbar.css", + "navStartToEndTime": 251, + "transferSize": 8587, + "children": {} + }, + "180065.92": { + "url": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "navStartToEndTime": 248, + "transferSize": 20091, + "children": {} + } + } + }, + "180065.79": { + "url": "http://localhost:5173/src/components/layout/PageLayout.css", + "navStartToEndTime": 237, + "transferSize": 1789, + "children": {} + } + } + }, + "180065.44": { + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "navStartToEndTime": 189, + "transferSize": 3750, + "children": { + "180065.76": { + "url": "http://localhost:5173/src/components/ui/Button.css", + "navStartToEndTime": 231, + "transferSize": 2160, + "children": {} + } + } + }, + "180065.47": { + "url": "http://localhost:5173/src/pages/Newsletter.css", + "navStartToEndTime": 194, + "transferSize": 4998, + "children": {} + }, + "180065.46": { + "url": "http://localhost:5173/src/utils/extractApiError.ts", + "navStartToEndTime": 191, + "transferSize": 7110, + "children": {} + }, + "180065.45": { + "url": "http://localhost:5173/src/services/newsletterService.ts", + "navStartToEndTime": 190, + "transferSize": 1363, + "children": {} + } + } + }, + "180065.32": { + "url": "http://localhost:5173/src/pages/Article.tsx", + "navStartToEndTime": 157, + "transferSize": 35202, + "children": { + "180065.64": { + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "navStartToEndTime": 220, + "transferSize": 26599, + "children": { + "180065.85": { + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "navStartToEndTime": 242, + "transferSize": 24618, + "children": { + "180065.95": { + "url": "http://localhost:5173/src/components/ui/CommentItem.css", + "navStartToEndTime": 249, + "transferSize": 4263, + "children": {} + } + } + }, + "180065.86": { + "url": "http://localhost:5173/src/services/commentService.ts", + "navStartToEndTime": 242, + "transferSize": 2883, + "children": {} + } + } + }, + "180065.59": { + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "navStartToEndTime": 212, + "transferSize": 4592, + "children": { + "180065.84": { + "url": "http://localhost:5173/src/components/ui/Avatar.css", + "navStartToEndTime": 241, + "transferSize": 1101, + "children": {} + } + } + }, + "180065.60": { + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "navStartToEndTime": 211, + "transferSize": 3273, + "children": { + "180065.83": { + "url": "http://localhost:5173/src/components/ui/Badge.css", + "navStartToEndTime": 240, + "transferSize": 2103, + "children": {} + } + } + }, + "180065.67": { + "url": "http://localhost:5173/src/pages/Article.css", + "navStartToEndTime": 221, + "transferSize": 13288, + "children": {} + }, + "180065.65": { + "url": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "navStartToEndTime": 219, + "transferSize": 6332, + "children": {} + }, + "180065.63": { + "url": "http://localhost:5173/src/utils/formatDate.ts", + "navStartToEndTime": 218, + "transferSize": 5113, + "children": {} + }, + "180065.61": { + "url": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "navStartToEndTime": 218, + "transferSize": 15920, + "children": {} + }, + "180065.62": { + "url": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "navStartToEndTime": 217, + "transferSize": 11522, + "children": {} + }, + "180065.66": { + "url": "http://localhost:5173/src/styles/article-body.css", + "navStartToEndTime": 216, + "transferSize": 2477, + "children": {} + } + } + }, + "180065.34": { + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "navStartToEndTime": 159, + "transferSize": 27959, + "children": { + "180065.73": { + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "navStartToEndTime": 232, + "transferSize": 13726, + "children": { + "180065.91": { + "url": "http://localhost:5173/src/components/ui/Modal.css", + "navStartToEndTime": 247, + "transferSize": 2244, + "children": {} + } + } + } + } + }, + "180065.36": { + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "navStartToEndTime": 159, + "transferSize": 18222, + "children": { + "180065.71": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "navStartToEndTime": 226, + "transferSize": 6890, + "children": { + "180065.90": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "navStartToEndTime": 247, + "transferSize": 1998, + "children": {} + } + } + }, + "180065.72": { + "url": "http://localhost:5173/src/utils/passwordRules.ts", + "navStartToEndTime": 225, + "transferSize": 3410, + "children": {} + } + } + }, + "180065.26": { + "url": "http://localhost:5173/src/pages/Home.tsx", + "navStartToEndTime": 154, + "transferSize": 22299, + "children": { + "180065.53": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "navStartToEndTime": 202, + "transferSize": 35878, + "children": { + "180065.81": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "navStartToEndTime": 239, + "transferSize": 3997, + "children": {} + } + } + }, + "180065.55": { + "url": "http://localhost:5173/src/pages/Home.css", + "navStartToEndTime": 205, + "transferSize": 9483, + "children": {} + }, + "180065.54": { + "url": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "navStartToEndTime": 203, + "transferSize": 689, + "children": {} + } + } + }, + "180065.30": { + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "navStartToEndTime": 155, + "transferSize": 4765, + "children": { + "180065.58": { + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "navStartToEndTime": 210, + "transferSize": 75711, + "children": { + "180065.82": { + "url": "http://localhost:5173/src/pages/Legal/Legal.css", + "navStartToEndTime": 239, + "transferSize": 4112, + "children": {} + } + } + } + } + }, + "180065.27": { + "url": "http://localhost:5173/src/pages/News.tsx", + "navStartToEndTime": 153, + "transferSize": 31303, + "children": { + "180065.49": { + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "navStartToEndTime": 197, + "transferSize": 12632, + "children": { + "180065.80": { + "url": "http://localhost:5173/src/components/ui/NewsCard.css", + "navStartToEndTime": 238, + "transferSize": 6902, + "children": {} + } + } + }, + "180065.51": { + "url": "http://localhost:5173/src/services/articleService.ts", + "navStartToEndTime": 199, + "transferSize": 9838, + "children": {} + }, + "180065.52": { + "url": "http://localhost:5173/src/pages/News.css", + "navStartToEndTime": 198, + "transferSize": 1882, + "children": {} + }, + "180065.50": { + "url": "http://localhost:5173/src/utils/categoryVariant.ts", + "navStartToEndTime": 197, + "transferSize": 2562, + "children": {} + } + } + }, + "180065.38": { + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "navStartToEndTime": 164, + "transferSize": 10579, + "children": { + "180065.75": { + "url": "http://localhost:5173/src/pages/NotFound.css", + "navStartToEndTime": 233, + "transferSize": 2228, + "children": {} + } + } + }, + "180065.37": { + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "navStartToEndTime": 161, + "transferSize": 55832, + "children": { + "180065.74": { + "url": "http://localhost:5173/src/pages/Perfil.css", + "navStartToEndTime": 231, + "transferSize": 4993, + "children": {} + } + } + }, + "180065.29": { + "url": "http://localhost:5173/src/pages/About/index.tsx", + "navStartToEndTime": 155, + "transferSize": 5790, + "children": { + "180065.57": { + "url": "http://localhost:5173/src/pages/About/About.css", + "navStartToEndTime": 209, + "transferSize": 3917, + "children": {} + }, + "180065.56": { + "url": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "navStartToEndTime": 208, + "transferSize": 15734, + "children": {} + } + } + }, + "180065.25": { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "navStartToEndTime": 152, + "transferSize": 9815, + "children": { + "180065.48": { + "url": "http://localhost:5173/src/components/ErrorFallback.css", + "navStartToEndTime": 192, + "transferSize": 3123, + "children": {} + } + } + }, + "180065.41": { + "url": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "navStartToEndTime": 167, + "transferSize": 10378, + "children": {} + }, + "180065.40": { + "url": "http://localhost:5173/src/router/AdminRoute.tsx", + "navStartToEndTime": 166, + "transferSize": 6218, + "children": {} + }, + "180065.39": { + "url": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "navStartToEndTime": 165, + "transferSize": 12734, + "children": {} + }, + "180065.23": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "navStartToEndTime": 163, + "transferSize": 399267, + "children": {} + }, + "180065.35": { + "url": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "navStartToEndTime": 160, + "transferSize": 12833, + "children": {} + }, + "180065.31": { + "url": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "navStartToEndTime": 156, + "transferSize": 4862, + "children": {} + }, + "180065.24": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "navStartToEndTime": 150, + "transferSize": 3458, + "children": {} + } + } + } + } + }, + "180065.10": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "navStartToEndTime": 104, + "transferSize": 821294, + "children": { + "180065.19": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "navStartToEndTime": 112, + "transferSize": 14940, + "children": {} + } + } + }, + "180065.11": { + "url": "http://localhost:5173/src/styles/global.css", + "navStartToEndTime": 88, + "transferSize": 16873, + "children": {} + }, + "180065.9": { + "url": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "navStartToEndTime": 74, + "transferSize": 38613, + "children": { + "180065.17": { + "url": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "navStartToEndTime": 86, + "transferSize": 7922, + "children": {} + } + } + }, + "180065.14": { + "url": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "navStartToEndTime": 75, + "transferSize": 10883, + "children": {} + } + } + }, + "180065.2": { + "url": "http://localhost:5173/@vite/client", + "navStartToEndTime": 108, + "transferSize": 210114, + "children": { + "180065.21": { + "url": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "navStartToEndTime": 123, + "transferSize": 3768, + "children": {} + } + } + }, + "180065.15": { + "url": "http://localhost:5173/site.webmanifest", + "navStartToEndTime": 116, + "transferSize": 796, + "children": {} + }, + "180065.8": { + "url": "http://localhost:5173/@react-refresh", + "navStartToEndTime": 65, + "transferSize": 112187, + "children": {} + } + } + } + }, + "longestChain": { + "duration": 644 + } + } + }, + { + "type": "list-section", + "title": "Preconnected origins", + "description": "[preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints help the browser establish a connection earlier in the page load, saving time when the first request for that origin is made. The following are the origins that the page preconnected to.", + "value": { + "type": "text", + "value": "no origins were preconnected" + } + }, + { + "type": "list-section", + "title": "Preconnect candidates", + "description": "Add [preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints to your most important origins, but try to use no more than 4.", + "value": { + "type": "text", + "value": "No additional origins are good candidates for preconnecting" + } + } + ] + }, + "guidanceLevel": 1, + "replacesAudits": ["critical-request-chains", "uses-rel-preconnect"] + }, + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources) can move these network requests out of the critical path.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Duration" + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["render-blocking-resources"] + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "3rd parties", + "description": "3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript/) to prioritize your page's content.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "3rd party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "mainThreadTime", + "granularity": 1, + "valueType": "ms", + "label": "Main thread time", + "subItemsHeading": { + "key": "mainThreadTime" + } + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["third-party-summary"] + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-7-META", + "path": "1,HTML,0,HEAD,7,META", + "selector": "head > meta", + "boundingRect": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "snippet": "", + "nodeLabel": "head > meta" + } + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["viewport"] + } + }, + "configSettings": { + "output": ["json"], + "maxWaitForFcp": 30000, + "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "formFactor": "desktop", + "throttling": { + "rttMs": 40, + "throughputKbps": 10240, + "requestLatencyMs": 0, + "downloadThroughputKbps": 0, + "uploadThroughputKbps": 0, + "cpuSlowdownMultiplier": 1 + }, + "throttlingMethod": "simulate", + "screenEmulation": { + "mobile": false, + "width": 1350, + "height": 940, + "deviceScaleFactor": 1, + "disabled": false + }, + "emulatedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", + "auditMode": false, + "gatherMode": false, + "clearStorageTypes": [ + "file_systems", + "shader_cache", + "service_workers", + "cache_storage" + ], + "disableStorageReset": false, + "debugNavigation": false, + "channel": "cli", + "usePassiveGathering": false, + "disableFullPageScreenshot": false, + "skipAboutBlank": false, + "blankPage": "about:blank", + "ignoreStatusCode": false, + "locale": "en-US", + "blockedUrlPatterns": null, + "additionalTraceCategories": null, + "extraHeaders": null, + "precomputedLanternData": null, + "onlyAudits": null, + "onlyCategories": null, + "skipAudits": null + }, + "categories": { + "performance": { + "title": "Performance", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "first-contentful-paint", + "weight": 10, + "group": "metrics", + "acronym": "FCP" + }, + { + "id": "largest-contentful-paint", + "weight": 25, + "group": "metrics", + "acronym": "LCP" + }, + { + "id": "total-blocking-time", + "weight": 30, + "group": "metrics", + "acronym": "TBT" + }, + { + "id": "cumulative-layout-shift", + "weight": 25, + "group": "metrics", + "acronym": "CLS" + }, + { + "id": "speed-index", + "weight": 10, + "group": "metrics", + "acronym": "SI" + }, + { + "id": "cache-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "duplicated-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "inp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "legacy-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "modern-http-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-dependency-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "third-parties-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interactive", + "weight": 0, + "group": "hidden", + "acronym": "TTI" + }, + { + "id": "max-potential-fid", + "weight": 0, + "group": "hidden" + }, + { + "id": "first-meaningful-paint", + "weight": 0, + "acronym": "FMP", + "group": "hidden" + }, + { + "id": "render-blocking-resources", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-responsive-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "offscreen-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-css", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-css-rules", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-optimized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "modern-image-formats", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-text-compression", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-rel-preconnect", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "server-response-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "redirects", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-http2", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "efficient-animated-content", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "duplicated-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "legacy-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "prioritize-lcp-image", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "total-byte-weight", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-long-cache-ttl", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "dom-size", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "critical-request-chains", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "user-timings", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bootup-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "mainthread-work-breakdown", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "font-display", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-summary", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-facades", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "largest-contentful-paint-element", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "lcp-lazy-loaded", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "layout-shifts", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-passive-event-listeners", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "no-document-write", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "long-tasks", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "non-composited-animations", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unsized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "viewport", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bf-cache", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "network-requests", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-rtt", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-server-latency", + "weight": 0, + "group": "hidden" + }, + { + "id": "main-thread-tasks", + "weight": 0, + "group": "hidden" + }, + { + "id": "diagnostics", + "weight": 0, + "group": "hidden" + }, + { + "id": "metrics", + "weight": 0, + "group": "hidden" + }, + { + "id": "screenshot-thumbnails", + "weight": 0, + "group": "hidden" + }, + { + "id": "final-screenshot", + "weight": 0, + "group": "hidden" + }, + { + "id": "script-treemap-data", + "weight": 0, + "group": "hidden" + }, + { + "id": "resource-summary", + "weight": 0, + "group": "hidden" + } + ], + "id": "performance", + "score": 0.67 + }, + "accessibility": { + "title": "Accessibility", + "description": "These checks highlight opportunities to [improve the accessibility of your web app](https://developer.chrome.com/docs/lighthouse/accessibility/). Automatic detection can only detect a subset of issues and does not guarantee the accessibility of your web app, so [manual testing](https://web.dev/articles/how-to-review) is also encouraged.", + "manualDescription": "These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://web.dev/articles/how-to-review).", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "accesskeys", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "aria-allowed-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-allowed-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-command-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-conditional-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-deprecated-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-dialog-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-body", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-focus", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-input-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-meter-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-progressbar-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-prohibited-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-required-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-required-children", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-required-parent", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-roles", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-text", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-toggle-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-tooltip-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-treeitem-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr-value", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "button-name", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "bypass", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "color-contrast", + "weight": 7, + "group": "a11y-color-contrast" + }, + { + "id": "definition-list", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "dlitem", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "document-title", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "duplicate-id-aria", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "form-field-multiple-labels", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "frame-title", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "heading-order", + "weight": 3, + "group": "a11y-navigation" + }, + { + "id": "html-has-lang", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-lang-valid", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-xml-lang-mismatch", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "image-alt", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "image-redundant-alt", + "weight": 1, + "group": "a11y-names-labels" + }, + { + "id": "input-button-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "input-image-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "label", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "link-in-text-block", + "weight": 0, + "group": "a11y-color-contrast" + }, + { + "id": "link-name", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "list", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "listitem", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "meta-refresh", + "weight": 0, + "group": "a11y-best-practices" + }, + { + "id": "meta-viewport", + "weight": 10, + "group": "a11y-best-practices" + }, + { + "id": "object-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "select-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "skip-link", + "weight": 3, + "group": "a11y-names-labels" + }, + { + "id": "tabindex", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "table-duplicate-name", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "target-size", + "weight": 7, + "group": "a11y-best-practices" + }, + { + "id": "td-headers-attr", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "th-has-data-cells", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "valid-lang", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "video-caption", + "weight": 0, + "group": "a11y-audio-video" + }, + { + "id": "focusable-controls", + "weight": 0 + }, + { + "id": "interactive-element-affordance", + "weight": 0 + }, + { + "id": "logical-tab-order", + "weight": 0 + }, + { + "id": "visual-order-follows-dom", + "weight": 0 + }, + { + "id": "focus-traps", + "weight": 0 + }, + { + "id": "managed-focus", + "weight": 0 + }, + { + "id": "use-landmarks", + "weight": 0 + }, + { + "id": "offscreen-content-hidden", + "weight": 0 + }, + { + "id": "custom-controls-labels", + "weight": 0 + }, + { + "id": "custom-controls-roles", + "weight": 0 + }, + { + "id": "empty-heading", + "weight": 0, + "group": "hidden" + }, + { + "id": "identical-links-same-purpose", + "weight": 0, + "group": "hidden" + }, + { + "id": "landmark-one-main", + "weight": 0, + "group": "hidden" + }, + { + "id": "label-content-name-mismatch", + "weight": 0, + "group": "hidden" + }, + { + "id": "table-fake-caption", + "weight": 0, + "group": "hidden" + }, + { + "id": "td-has-header", + "weight": 0, + "group": "hidden" + } + ], + "id": "accessibility", + "score": 1 + }, + "best-practices": { + "title": "Best Practices", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "is-on-https", + "weight": 5, + "group": "best-practices-trust-safety" + }, + { + "id": "redirects-http", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "geolocation-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "notification-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "csp-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "has-hsts", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "origin-isolation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "clickjacking-mitigation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "trusted-types-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "paste-preventing-inputs", + "weight": 3, + "group": "best-practices-ux" + }, + { + "id": "image-aspect-ratio", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "image-size-responsive", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "viewport", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "font-size", + "weight": 0, + "group": "best-practices-ux" + }, + { + "id": "doctype", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "charset", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "js-libraries", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "deprecations", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "third-party-cookies", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "errors-in-console", + "weight": 1, + "group": "best-practices-general" + }, + { + "id": "valid-source-maps", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "inspector-issues", + "weight": 1, + "group": "best-practices-general" + } + ], + "id": "best-practices", + "score": 0.96 + }, + "seo": { + "title": "SEO", + "description": "These checks ensure that your page is following basic search engine optimization advice. There are many additional factors Lighthouse does not score here that may affect your search ranking, including performance on [Core Web Vitals](https://web.dev/explore/vitals). [Learn more about Google Search Essentials](https://support.google.com/webmasters/answer/35769).", + "manualDescription": "Run these additional validators on your site to check additional SEO best practices.", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "is-crawlable", + "weight": 4.043478260869565, + "group": "seo-crawl" + }, + { + "id": "document-title", + "weight": 1, + "group": "seo-content" + }, + { + "id": "meta-description", + "weight": 1, + "group": "seo-content" + }, + { + "id": "http-status-code", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "link-text", + "weight": 1, + "group": "seo-content" + }, + { + "id": "crawlable-anchors", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "robots-txt", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "image-alt", + "weight": 1, + "group": "seo-content" + }, + { + "id": "hreflang", + "weight": 1, + "group": "seo-content" + }, + { + "id": "canonical", + "weight": 0, + "group": "seo-content" + }, + { + "id": "structured-data", + "weight": 0 + } + ], + "id": "seo", + "score": 0.92 + } + }, + "categoryGroups": { + "metrics": { + "title": "Metrics" + }, + "insights": { + "title": "Insights", + "description": "These insights are also available in the Chrome DevTools Performance Panel - [record a trace](https://developer.chrome.com/docs/devtools/performance/reference) to view more detailed information." + }, + "diagnostics": { + "title": "Diagnostics", + "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." + }, + "a11y-best-practices": { + "title": "Best practices", + "description": "These items highlight common accessibility best practices." + }, + "a11y-color-contrast": { + "title": "Contrast", + "description": "These are opportunities to improve the legibility of your content." + }, + "a11y-names-labels": { + "title": "Names and labels", + "description": "These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-navigation": { + "title": "Navigation", + "description": "These are opportunities to improve keyboard navigation in your application." + }, + "a11y-aria": { + "title": "ARIA", + "description": "These are opportunities to improve the usage of ARIA in your application which may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-language": { + "title": "Internationalization and localization", + "description": "These are opportunities to improve the interpretation of your content by users in different locales." + }, + "a11y-audio-video": { + "title": "Audio and video", + "description": "These are opportunities to provide alternative content for audio and video. This may improve the experience for users with hearing or vision impairments." + }, + "a11y-tables-lists": { + "title": "Tables and lists", + "description": "These are opportunities to improve the experience of reading tabular or list data using assistive technology, like a screen reader." + }, + "seo-mobile": { + "title": "Mobile Friendly", + "description": "Make sure your pages are mobile friendly so users don’t have to pinch or zoom in order to read the content pages. [Learn how to make pages mobile-friendly](https://developers.google.com/search/mobile-sites/)." + }, + "seo-content": { + "title": "Content Best Practices", + "description": "Format your HTML in a way that enables crawlers to better understand your app’s content." + }, + "seo-crawl": { + "title": "Crawling and Indexing", + "description": "To appear in search results, crawlers need access to your app." + }, + "best-practices-trust-safety": { + "title": "Trust and Safety" + }, + "best-practices-ux": { + "title": "User Experience" + }, + "best-practices-browser-compat": { + "title": "Browser Compatibility" + }, + "best-practices-general": { + "title": "General" + }, + "hidden": { + "title": "" + } + }, + "stackPacks": [], + "entities": [ + { + "name": "localhost", + "origins": ["http://localhost:5173", "http://localhost:8000"], + "isFirstParty": true, + "isUnrecognized": true + }, + { + "name": "vlibras.gov.br", + "origins": ["https://vlibras.gov.br"], + "isUnrecognized": true + } + ], + "fullPageScreenshot": { + "screenshot": { + "data": "data:image/webp;base64,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", + "width": 765, + "height": 1306 + }, + "nodes": { + "page-0-H1": { + "id": "hero-manifesto", + "top": 136, + "bottom": 239, + "left": 24, + "right": 595, + "width": 571, + "height": 102 + }, + "page-1-SECTION": { + "id": "", + "top": 569, + "bottom": 941, + "left": 0, + "right": 765, + "width": 765, + "height": 372 + }, + "page-2-P": { + "id": "", + "top": 259, + "bottom": 340, + "left": 24, + "right": 549, + "width": 525, + "height": 82 + }, + "page-3-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 193, + "right": 573, + "width": 380, + "height": 111 + }, + "page-4-DIV": { + "id": "", + "top": 829, + "bottom": 877, + "left": 24, + "right": 741, + "width": 717, + "height": 48 + }, + "page-5-FOOTER": { + "id": "", + "top": 941, + "bottom": 1498, + "left": 0, + "right": 765, + "width": 765, + "height": 558 + }, + "page-6-IMG": { + "id": "", + "top": 1434, + "bottom": 1474, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "page-7-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "page-8-UL": { + "id": "", + "top": 1185, + "bottom": 1361, + "left": 24, + "right": 242, + "width": 218, + "height": 176 + }, + "page-9-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "page-10-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-0-A": { + "id": "", + "top": -81, + "bottom": -34, + "left": 12, + "right": 194, + "width": 182, + "height": 46 + }, + "1-1-A": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-2-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-3-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-4-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-5-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-6-A": { + "id": "", + "top": 586, + "bottom": 609, + "left": 651, + "right": 741, + "width": 90, + "height": 22 + }, + "1-7-A": { + "id": "", + "top": 829, + "bottom": 877, + "left": 268, + "right": 497, + "width": 229, + "height": 48 + }, + "1-8-A": { + "id": "", + "top": 1189, + "bottom": 1206, + "left": 24, + "right": 72, + "width": 48, + "height": 17 + }, + "1-9-A": { + "id": "", + "top": 1227, + "bottom": 1244, + "left": 24, + "right": 61, + "width": 37, + "height": 17 + }, + "1-10-A": { + "id": "", + "top": 1265, + "bottom": 1282, + "left": 24, + "right": 74, + "width": 50, + "height": 17 + }, + "1-11-A": { + "id": "", + "top": 1302, + "bottom": 1319, + "left": 24, + "right": 87, + "width": 63, + "height": 17 + }, + "1-12-A": { + "id": "", + "top": 1340, + "bottom": 1357, + "left": 24, + "right": 117, + "width": 93, + "height": 17 + }, + "1-13-A": { + "id": "", + "top": 1189, + "bottom": 1206, + "left": 274, + "right": 341, + "width": 67, + "height": 17 + }, + "1-14-A": { + "id": "", + "top": 1227, + "bottom": 1244, + "left": 274, + "right": 331, + "width": 58, + "height": 17 + }, + "1-15-A": { + "id": "", + "top": 1265, + "bottom": 1282, + "left": 274, + "right": 328, + "width": 54, + "height": 17 + }, + "1-16-A": { + "id": "", + "top": 1302, + "bottom": 1319, + "left": 274, + "right": 326, + "width": 52, + "height": 17 + }, + "1-17-A": { + "id": "", + "top": 1189, + "bottom": 1206, + "left": 523, + "right": 563, + "width": 40, + "height": 17 + }, + "1-18-A": { + "id": "", + "top": 1227, + "bottom": 1244, + "left": 523, + "right": 597, + "width": 73, + "height": 17 + }, + "1-19-A": { + "id": "", + "top": 1265, + "bottom": 1282, + "left": 523, + "right": 636, + "width": 112, + "height": 17 + }, + "1-20-A": { + "id": "", + "top": 1434, + "bottom": 1474, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-21-A": { + "id": "", + "top": 1445, + "bottom": 1464, + "left": 378, + "right": 445, + "width": 67, + "height": 19 + }, + "1-22-A": { + "id": "", + "top": 1445, + "bottom": 1464, + "left": 465, + "right": 549, + "width": 84, + "height": 19 + }, + "1-23-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "1-24-BODY": { + "id": "", + "top": 0, + "bottom": 1498, + "left": 0, + "right": 765, + "width": 765, + "height": 1498 + }, + "1-25-FORM": { + "id": "", + "top": 1075, + "bottom": 1118, + "left": 24, + "right": 741, + "width": 717, + "height": 43 + }, + "1-26-INPUT": { + "id": "", + "top": 1075, + "bottom": 1118, + "left": 24, + "right": 626, + "width": 602, + "height": 43 + }, + "1-27-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-28-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 193, + "right": 573, + "width": 380, + "height": 111 + }, + "1-29-IMG": { + "id": "", + "top": 1434, + "bottom": 1474, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-30-IMG": { + "id": "", + "top": 1445, + "bottom": 1463, + "left": 701, + "right": 741, + "width": 40, + "height": 18 + }, + "1-31-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-32-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-33-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-34-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-35-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-36-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-37-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-38-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-39-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-40-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-41-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + } + } + }, + "timing": { + "entries": [ + { + "startTime": 1393.39, + "name": "lh:config", + "duration": 896.31, + "entryType": "measure" + }, + { + "startTime": 1395.64, + "name": "lh:config:resolveArtifactsToDefns", + "duration": 66.8, + "entryType": "measure" + }, + { + "startTime": 2289.9, + "name": "lh:runner:gather", + "duration": 9115.86, + "entryType": "measure" + }, + { + "startTime": 2384.84, + "name": "lh:driver:connect", + "duration": 9.51, + "entryType": "measure" + }, + { + "startTime": 2394.64, + "name": "lh:driver:navigate", + "duration": 8.57, + "entryType": "measure" + }, + { + "startTime": 2403.76, + "name": "lh:gather:getBenchmarkIndex", + "duration": 1010.38, + "entryType": "measure" + }, + { + "startTime": 3414.31, + "name": "lh:gather:getVersion", + "duration": 1.08, + "entryType": "measure" + }, + { + "startTime": 3415.54, + "name": "lh:gather:getDevicePixelRatio", + "duration": 1.48, + "entryType": "measure" + }, + { + "startTime": 3417.49, + "name": "lh:prepare:navigationMode", + "duration": 45.98, + "entryType": "measure" + }, + { + "startTime": 3443.69, + "name": "lh:storage:clearDataForOrigin", + "duration": 7.82, + "entryType": "measure" + }, + { + "startTime": 3451.66, + "name": "lh:storage:clearBrowserCaches", + "duration": 7.45, + "entryType": "measure" + }, + { + "startTime": 3461.52, + "name": "lh:gather:prepareThrottlingAndNetwork", + "duration": 1.91, + "entryType": "measure" + }, + { + "startTime": 3497.24, + "name": "lh:driver:navigate", + "duration": 3019.2, + "entryType": "measure" + }, + { + "startTime": 7189.69, + "name": "lh:computed:NetworkRecords", + "duration": 3.13, + "entryType": "measure" + }, + { + "startTime": 7193.51, + "name": "lh:gather:getArtifact:DevtoolsLog", + "duration": 0.07, + "entryType": "measure" + }, + { + "startTime": 7193.61, + "name": "lh:gather:getArtifact:Trace", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 7193.66, + "name": "lh:gather:getArtifact:Accessibility", + "duration": 434.8, + "entryType": "measure" + }, + { + "startTime": 7628.5, + "name": "lh:gather:getArtifact:AnchorElements", + "duration": 369.38, + "entryType": "measure" + }, + { + "startTime": 7997.93, + "name": "lh:gather:getArtifact:ConsoleMessages", + "duration": 0.07, + "entryType": "measure" + }, + { + "startTime": 7998.02, + "name": "lh:gather:getArtifact:CSSUsage", + "duration": 45.1, + "entryType": "measure" + }, + { + "startTime": 8043.15, + "name": "lh:gather:getArtifact:Doctype", + "duration": 1.66, + "entryType": "measure" + }, + { + "startTime": 8044.84, + "name": "lh:gather:getArtifact:DOMStats", + "duration": 7.98, + "entryType": "measure" + }, + { + "startTime": 8052.86, + "name": "lh:gather:getArtifact:FontSize", + "duration": 35.18, + "entryType": "measure" + }, + { + "startTime": 8088.06, + "name": "lh:gather:getArtifact:Inputs", + "duration": 6.58, + "entryType": "measure" + }, + { + "startTime": 8094.67, + "name": "lh:gather:getArtifact:ImageElements", + "duration": 84.44, + "entryType": "measure" + }, + { + "startTime": 8179.33, + "name": "lh:gather:getArtifact:InspectorIssues", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 8179.68, + "name": "lh:gather:getArtifact:JsUsage", + "duration": 0.15, + "entryType": "measure" + }, + { + "startTime": 8179.87, + "name": "lh:gather:getArtifact:LinkElements", + "duration": 6.3, + "entryType": "measure" + }, + { + "startTime": 8185.81, + "name": "lh:computed:MainResource", + "duration": 0.3, + "entryType": "measure" + }, + { + "startTime": 8186.23, + "name": "lh:gather:getArtifact:MainDocumentContent", + "duration": 5.2, + "entryType": "measure" + }, + { + "startTime": 8191.47, + "name": "lh:gather:getArtifact:MetaElements", + "duration": 10.28, + "entryType": "measure" + }, + { + "startTime": 8201.82, + "name": "lh:gather:getArtifact:NetworkUserAgent", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 8202.01, + "name": "lh:gather:getArtifact:OptimizedImages", + "duration": 0.66, + "entryType": "measure" + }, + { + "startTime": 8202.71, + "name": "lh:gather:getArtifact:ResponseCompression", + "duration": 231.69, + "entryType": "measure" + }, + { + "startTime": 8434.46, + "name": "lh:gather:getArtifact:RobotsTxt", + "duration": 19.82, + "entryType": "measure" + }, + { + "startTime": 8454.31, + "name": "lh:gather:getArtifact:Scripts", + "duration": 0.37, + "entryType": "measure" + }, + { + "startTime": 8454.72, + "name": "lh:gather:getArtifact:SourceMaps", + "duration": 174.45, + "entryType": "measure" + }, + { + "startTime": 8629.22, + "name": "lh:gather:getArtifact:Stacks", + "duration": 45.41, + "entryType": "measure" + }, + { + "startTime": 8629.4, + "name": "lh:gather:collectStacks", + "duration": 45.19, + "entryType": "measure" + }, + { + "startTime": 8674.65, + "name": "lh:gather:getArtifact:Stylesheets", + "duration": 45.05, + "entryType": "measure" + }, + { + "startTime": 8719.89, + "name": "lh:gather:getArtifact:TraceElements", + "duration": 846.69, + "entryType": "measure" + }, + { + "startTime": 8720.2, + "name": "lh:computed:TraceEngineResult", + "duration": 738.04, + "entryType": "measure" + }, + { + "startTime": 8720.34, + "name": "lh:computed:ProcessedTrace", + "duration": 80.8, + "entryType": "measure" + }, + { + "startTime": 8804.12, + "name": "lh:computed:TraceEngineResult:total", + "duration": 620.07, + "entryType": "measure" + }, + { + "startTime": 8804.17, + "name": "lh:computed:TraceEngineResult:parse", + "duration": 437.49, + "entryType": "measure" + }, + { + "startTime": 8805.83, + "name": "lh:computed:TraceEngineResult:parse:handleEvent", + "duration": 300.12, + "entryType": "measure" + }, + { + "startTime": 9106.02, + "name": "lh:computed:TraceEngineResult:parse:Meta:finalize", + "duration": 4, + "entryType": "measure" + }, + { + "startTime": 9110.33, + "name": "lh:computed:TraceEngineResult:parse:AnimationFrames:finalize", + "duration": 1.39, + "entryType": "measure" + }, + { + "startTime": 9111.78, + "name": "lh:computed:TraceEngineResult:parse:Animations:finalize", + "duration": 2.21, + "entryType": "measure" + }, + { + "startTime": 9114.04, + "name": "lh:computed:TraceEngineResult:parse:Samples:finalize", + "duration": 1.35, + "entryType": "measure" + }, + { + "startTime": 9115.44, + "name": "lh:computed:TraceEngineResult:parse:AuctionWorklets:finalize", + "duration": 1.35, + "entryType": "measure" + }, + { + "startTime": 9116.84, + "name": "lh:computed:TraceEngineResult:parse:NetworkRequests:finalize", + "duration": 14.03, + "entryType": "measure" + }, + { + "startTime": 9130.92, + "name": "lh:computed:TraceEngineResult:parse:Renderer:finalize", + "duration": 28.38, + "entryType": "measure" + }, + { + "startTime": 9159.4, + "name": "lh:computed:TraceEngineResult:parse:Flows:finalize", + "duration": 11.82, + "entryType": "measure" + }, + { + "startTime": 9171.28, + "name": "lh:computed:TraceEngineResult:parse:AsyncJSCalls:finalize", + "duration": 2.2, + "entryType": "measure" + }, + { + "startTime": 9173.61, + "name": "lh:computed:TraceEngineResult:parse:DOMStats:finalize", + "duration": 1.15, + "entryType": "measure" + }, + { + "startTime": 9174.82, + "name": "lh:computed:TraceEngineResult:parse:UserTimings:finalize", + "duration": 2.42, + "entryType": "measure" + }, + { + "startTime": 9177.28, + "name": "lh:computed:TraceEngineResult:parse:ExtensionTraceData:finalize", + "duration": 5.74, + "entryType": "measure" + }, + { + "startTime": 9183.06, + "name": "lh:computed:TraceEngineResult:parse:LayerTree:finalize", + "duration": 4.94, + "entryType": "measure" + }, + { + "startTime": 9188.03, + "name": "lh:computed:TraceEngineResult:parse:Frames:finalize", + "duration": 17.78, + "entryType": "measure" + }, + { + "startTime": 9205.87, + "name": "lh:computed:TraceEngineResult:parse:GPU:finalize", + "duration": 1.33, + "entryType": "measure" + }, + { + "startTime": 9207.23, + "name": "lh:computed:TraceEngineResult:parse:ImagePainting:finalize", + "duration": 1.25, + "entryType": "measure" + }, + { + "startTime": 9208.51, + "name": "lh:computed:TraceEngineResult:parse:Initiators:finalize", + "duration": 1.84, + "entryType": "measure" + }, + { + "startTime": 9210.39, + "name": "lh:computed:TraceEngineResult:parse:Invalidations:finalize", + "duration": 1.19, + "entryType": "measure" + }, + { + "startTime": 9211.6, + "name": "lh:computed:TraceEngineResult:parse:PageLoadMetrics:finalize", + "duration": 2.7, + "entryType": "measure" + }, + { + "startTime": 9214.33, + "name": "lh:computed:TraceEngineResult:parse:LargestImagePaint:finalize", + "duration": 1.87, + "entryType": "measure" + }, + { + "startTime": 9216.25, + "name": "lh:computed:TraceEngineResult:parse:LargestTextPaint:finalize", + "duration": 1.37, + "entryType": "measure" + }, + { + "startTime": 9217.69, + "name": "lh:computed:TraceEngineResult:parse:Screenshots:finalize", + "duration": 7.77, + "entryType": "measure" + }, + { + "startTime": 9225.62, + "name": "lh:computed:TraceEngineResult:parse:LayoutShifts:finalize", + "duration": 3.03, + "entryType": "measure" + }, + { + "startTime": 9228.69, + "name": "lh:computed:TraceEngineResult:parse:Memory:finalize", + "duration": 1.14, + "entryType": "measure" + }, + { + "startTime": 9229.85, + "name": "lh:computed:TraceEngineResult:parse:PageFrames:finalize", + "duration": 1.12, + "entryType": "measure" + }, + { + "startTime": 9230.99, + "name": "lh:computed:TraceEngineResult:parse:Scripts:finalize", + "duration": 2.31, + "entryType": "measure" + }, + { + "startTime": 9233.33, + "name": "lh:computed:TraceEngineResult:parse:SelectorStats:finalize", + "duration": 1.16, + "entryType": "measure" + }, + { + "startTime": 9234.51, + "name": "lh:computed:TraceEngineResult:parse:UserInteractions:finalize", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 9236, + "name": "lh:computed:TraceEngineResult:parse:Workers:finalize", + "duration": 1.16, + "entryType": "measure" + }, + { + "startTime": 9237.19, + "name": "lh:computed:TraceEngineResult:parse:Warnings:finalize", + "duration": 1.37, + "entryType": "measure" + }, + { + "startTime": 9238.59, + "name": "lh:computed:TraceEngineResult:parse:clone", + "duration": 3.04, + "entryType": "measure" + }, + { + "startTime": 9241.67, + "name": "lh:computed:TraceEngineResult:insights", + "duration": 182.49, + "entryType": "measure" + }, + { + "startTime": 9242.24, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 0.81, + "entryType": "measure" + }, + { + "startTime": 9243.08, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 0.37, + "entryType": "measure" + }, + { + "startTime": 9243.47, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 1.2, + "entryType": "measure" + }, + { + "startTime": 9244.7, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.29, + "entryType": "measure" + }, + { + "startTime": 9245.01, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 3.46, + "entryType": "measure" + }, + { + "startTime": 9248.51, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.29, + "entryType": "measure" + }, + { + "startTime": 9248.82, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.34, + "entryType": "measure" + }, + { + "startTime": 9249.19, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 9249.45, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.49, + "entryType": "measure" + }, + { + "startTime": 9249.97, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 9250.21, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 4.14, + "entryType": "measure" + }, + { + "startTime": 9254.38, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 0.45, + "entryType": "measure" + }, + { + "startTime": 9254.85, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 0.46, + "entryType": "measure" + }, + { + "startTime": 9255.34, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 0.19, + "entryType": "measure" + }, + { + "startTime": 9255.55, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 9255.84, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 9256.19, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 2.73, + "entryType": "measure" + }, + { + "startTime": 9258.95, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 9259.58, + "name": "lh:computed:TraceEngineResult:insights:createLanternContext", + "duration": 110.94, + "entryType": "measure" + }, + { + "startTime": 9370.61, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 1.26, + "entryType": "measure" + }, + { + "startTime": 9371.91, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 1.74, + "entryType": "measure" + }, + { + "startTime": 9373.68, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 9373.97, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.55, + "entryType": "measure" + }, + { + "startTime": 9374.54, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 0.44, + "entryType": "measure" + }, + { + "startTime": 9374.99, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 9375.16, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.05, + "entryType": "measure" + }, + { + "startTime": 9375.23, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.03, + "entryType": "measure" + }, + { + "startTime": 9375.26, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.31, + "entryType": "measure" + }, + { + "startTime": 9375.6, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.35, + "entryType": "measure" + }, + { + "startTime": 9375.96, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 9376.01, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 13.08, + "entryType": "measure" + }, + { + "startTime": 9389.13, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 9.23, + "entryType": "measure" + }, + { + "startTime": 9398.39, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 4.88, + "entryType": "measure" + }, + { + "startTime": 9403.3, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 0.65, + "entryType": "measure" + }, + { + "startTime": 9403.97, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 9404.02, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 18.87, + "entryType": "measure" + }, + { + "startTime": 9422.92, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.06, + "entryType": "measure" + }, + { + "startTime": 9475.44, + "name": "lh:computed:ProcessedNavigation", + "duration": 2.14, + "entryType": "measure" + }, + { + "startTime": 9477.72, + "name": "lh:computed:CumulativeLayoutShift", + "duration": 12.11, + "entryType": "measure" + }, + { + "startTime": 9491.16, + "name": "lh:computed:Responsiveness", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 9566.63, + "name": "lh:gather:getArtifact:ViewportDimensions", + "duration": 6.19, + "entryType": "measure" + }, + { + "startTime": 9572.86, + "name": "lh:gather:getArtifact:FullPageScreenshot", + "duration": 1299.21, + "entryType": "measure" + }, + { + "startTime": 10872.15, + "name": "lh:gather:getArtifact:BFCacheFailures", + "duration": 403.69, + "entryType": "measure" + }, + { + "startTime": 11409.96, + "name": "lh:runner:audit", + "duration": 2273.25, + "entryType": "measure" + }, + { + "startTime": 11412.8, + "name": "lh:runner:auditing", + "duration": 2269.73, + "entryType": "measure" + }, + { + "startTime": 11417.5, + "name": "lh:audit:is-on-https", + "duration": 14.62, + "entryType": "measure" + }, + { + "startTime": 11432.66, + "name": "lh:audit:redirects-http", + "duration": 4.48, + "entryType": "measure" + }, + { + "startTime": 11439.09, + "name": "lh:audit:viewport", + "duration": 11.55, + "entryType": "measure" + }, + { + "startTime": 11443.24, + "name": "lh:computed:ViewportMeta", + "duration": 0.91, + "entryType": "measure" + }, + { + "startTime": 11451.11, + "name": "lh:audit:first-contentful-paint", + "duration": 126.03, + "entryType": "measure" + }, + { + "startTime": 11453.83, + "name": "lh:computed:FirstContentfulPaint", + "duration": 119.69, + "entryType": "measure" + }, + { + "startTime": 11454.28, + "name": "lh:computed:LanternFirstContentfulPaint", + "duration": 119.2, + "entryType": "measure" + }, + { + "startTime": 11454.48, + "name": "lh:computed:PageDependencyGraph", + "duration": 76.8, + "entryType": "measure" + }, + { + "startTime": 11531.36, + "name": "lh:computed:LoadSimulator", + "duration": 10.89, + "entryType": "measure" + }, + { + "startTime": 11531.48, + "name": "lh:computed:NetworkAnalysis", + "duration": 10.64, + "entryType": "measure" + }, + { + "startTime": 11577.61, + "name": "lh:audit:largest-contentful-paint", + "duration": 44.81, + "entryType": "measure" + }, + { + "startTime": 11578.55, + "name": "lh:computed:LargestContentfulPaint", + "duration": 41.95, + "entryType": "measure" + }, + { + "startTime": 11578.69, + "name": "lh:computed:LanternLargestContentfulPaint", + "duration": 41.78, + "entryType": "measure" + }, + { + "startTime": 11622.85, + "name": "lh:audit:first-meaningful-paint", + "duration": 3.06, + "entryType": "measure" + }, + { + "startTime": 11626.45, + "name": "lh:audit:speed-index", + "duration": 347.84, + "entryType": "measure" + }, + { + "startTime": 11626.98, + "name": "lh:computed:SpeedIndex", + "duration": 345.97, + "entryType": "measure" + }, + { + "startTime": 11627.1, + "name": "lh:computed:LanternSpeedIndex", + "duration": 345.83, + "entryType": "measure" + }, + { + "startTime": 11627.17, + "name": "lh:computed:Speedline", + "duration": 325.91, + "entryType": "measure" + }, + { + "startTime": 11974.35, + "name": "lh:audit:screenshot-thumbnails", + "duration": 0.71, + "entryType": "measure" + }, + { + "startTime": 11975.09, + "name": "lh:audit:final-screenshot", + "duration": 1.92, + "entryType": "measure" + }, + { + "startTime": 11975.24, + "name": "lh:computed:Screenshots", + "duration": 1.67, + "entryType": "measure" + }, + { + "startTime": 11977.55, + "name": "lh:audit:total-blocking-time", + "duration": 34.82, + "entryType": "measure" + }, + { + "startTime": 11978.16, + "name": "lh:computed:TotalBlockingTime", + "duration": 32.68, + "entryType": "measure" + }, + { + "startTime": 11978.28, + "name": "lh:computed:LanternTotalBlockingTime", + "duration": 32.53, + "entryType": "measure" + }, + { + "startTime": 11978.5, + "name": "lh:computed:LanternInteractive", + "duration": 16.9, + "entryType": "measure" + }, + { + "startTime": 12014.34, + "name": "lh:audit:max-potential-fid", + "duration": 18.17, + "entryType": "measure" + }, + { + "startTime": 12015.5, + "name": "lh:computed:MaxPotentialFID", + "duration": 11.41, + "entryType": "measure" + }, + { + "startTime": 12015.67, + "name": "lh:computed:LanternMaxPotentialFID", + "duration": 11.21, + "entryType": "measure" + }, + { + "startTime": 12032.92, + "name": "lh:audit:cumulative-layout-shift", + "duration": 4.73, + "entryType": "measure" + }, + { + "startTime": 12038.16, + "name": "lh:audit:errors-in-console", + "duration": 250.41, + "entryType": "measure" + }, + { + "startTime": 12038.84, + "name": "lh:computed:JSBundles", + "duration": 245.86, + "entryType": "measure" + }, + { + "startTime": 12289.07, + "name": "lh:audit:server-response-time", + "duration": 2.67, + "entryType": "measure" + }, + { + "startTime": 12292.16, + "name": "lh:audit:interactive", + "duration": 2.04, + "entryType": "measure" + }, + { + "startTime": 12292.74, + "name": "lh:computed:Interactive", + "duration": 0.15, + "entryType": "measure" + }, + { + "startTime": 12294.54, + "name": "lh:audit:user-timings", + "duration": 7.02, + "entryType": "measure" + }, + { + "startTime": 12294.99, + "name": "lh:computed:UserTimings", + "duration": 2.49, + "entryType": "measure" + }, + { + "startTime": 12305.76, + "name": "lh:audit:critical-request-chains", + "duration": 29.47, + "entryType": "measure" + }, + { + "startTime": 12306.91, + "name": "lh:computed:CriticalRequestChains", + "duration": 24.5, + "entryType": "measure" + }, + { + "startTime": 12335.71, + "name": "lh:audit:redirects", + "duration": 4.55, + "entryType": "measure" + }, + { + "startTime": 12340.91, + "name": "lh:audit:image-aspect-ratio", + "duration": 2.87, + "entryType": "measure" + }, + { + "startTime": 12344.49, + "name": "lh:audit:image-size-responsive", + "duration": 3.75, + "entryType": "measure" + }, + { + "startTime": 12345.36, + "name": "lh:computed:ImageRecords", + "duration": 0.61, + "entryType": "measure" + }, + { + "startTime": 12348.87, + "name": "lh:audit:deprecations", + "duration": 2.85, + "entryType": "measure" + }, + { + "startTime": 12352.37, + "name": "lh:audit:third-party-cookies", + "duration": 2.53, + "entryType": "measure" + }, + { + "startTime": 12355.55, + "name": "lh:audit:mainthread-work-breakdown", + "duration": 26.15, + "entryType": "measure" + }, + { + "startTime": 12356.67, + "name": "lh:computed:MainThreadTasks", + "duration": 22.61, + "entryType": "measure" + }, + { + "startTime": 12382.09, + "name": "lh:audit:bootup-time", + "duration": 15.02, + "entryType": "measure" + }, + { + "startTime": 12385.1, + "name": "lh:computed:TBTImpactTasks", + "duration": 5.82, + "entryType": "measure" + }, + { + "startTime": 12397.49, + "name": "lh:audit:uses-rel-preconnect", + "duration": 3.94, + "entryType": "measure" + }, + { + "startTime": 12401.81, + "name": "lh:audit:font-display", + "duration": 4.32, + "entryType": "measure" + }, + { + "startTime": 12406.16, + "name": "lh:audit:diagnostics", + "duration": 1.38, + "entryType": "measure" + }, + { + "startTime": 12407.56, + "name": "lh:audit:network-requests", + "duration": 6.69, + "entryType": "measure" + }, + { + "startTime": 12407.87, + "name": "lh:computed:EntityClassification", + "duration": 3.68, + "entryType": "measure" + }, + { + "startTime": 12414.61, + "name": "lh:audit:network-rtt", + "duration": 1.44, + "entryType": "measure" + }, + { + "startTime": 12416.34, + "name": "lh:audit:network-server-latency", + "duration": 1.49, + "entryType": "measure" + }, + { + "startTime": 12417.86, + "name": "lh:audit:main-thread-tasks", + "duration": 0.45, + "entryType": "measure" + }, + { + "startTime": 12418.32, + "name": "lh:audit:metrics", + "duration": 3.32, + "entryType": "measure" + }, + { + "startTime": 12418.49, + "name": "lh:computed:TimingSummary", + "duration": 2.97, + "entryType": "measure" + }, + { + "startTime": 12418.89, + "name": "lh:computed:FirstContentfulPaintAllFrames", + "duration": 0.1, + "entryType": "measure" + }, + { + "startTime": 12419.03, + "name": "lh:computed:LargestContentfulPaintAllFrames", + "duration": 0.08, + "entryType": "measure" + }, + { + "startTime": 12419.24, + "name": "lh:computed:LCPBreakdown", + "duration": 1.52, + "entryType": "measure" + }, + { + "startTime": 12419.35, + "name": "lh:computed:TimeToFirstByte", + "duration": 0.2, + "entryType": "measure" + }, + { + "startTime": 12419.58, + "name": "lh:computed:LCPImageRecord", + "duration": 1.15, + "entryType": "measure" + }, + { + "startTime": 12421.66, + "name": "lh:audit:resource-summary", + "duration": 1.75, + "entryType": "measure" + }, + { + "startTime": 12421.85, + "name": "lh:computed:ResourceSummary", + "duration": 0.7, + "entryType": "measure" + }, + { + "startTime": 12423.8, + "name": "lh:audit:third-party-summary", + "duration": 4.7, + "entryType": "measure" + }, + { + "startTime": 12428.91, + "name": "lh:audit:third-party-facades", + "duration": 5.79, + "entryType": "measure" + }, + { + "startTime": 12434.99, + "name": "lh:audit:largest-contentful-paint-element", + "duration": 2.13, + "entryType": "measure" + }, + { + "startTime": 12437.54, + "name": "lh:audit:lcp-lazy-loaded", + "duration": 1.38, + "entryType": "measure" + }, + { + "startTime": 12439.24, + "name": "lh:audit:layout-shifts", + "duration": 2.32, + "entryType": "measure" + }, + { + "startTime": 12441.85, + "name": "lh:audit:long-tasks", + "duration": 8.25, + "entryType": "measure" + }, + { + "startTime": 12450.39, + "name": "lh:audit:non-composited-animations", + "duration": 1.37, + "entryType": "measure" + }, + { + "startTime": 12452.12, + "name": "lh:audit:unsized-images", + "duration": 2.38, + "entryType": "measure" + }, + { + "startTime": 12454.94, + "name": "lh:audit:valid-source-maps", + "duration": 3.14, + "entryType": "measure" + }, + { + "startTime": 12458.58, + "name": "lh:audit:prioritize-lcp-image", + "duration": 1.4, + "entryType": "measure" + }, + { + "startTime": 12460.35, + "name": "lh:audit:csp-xss", + "duration": 1.45, + "entryType": "measure" + }, + { + "startTime": 12462.06, + "name": "lh:audit:has-hsts", + "duration": 1.36, + "entryType": "measure" + }, + { + "startTime": 12463.68, + "name": "lh:audit:origin-isolation", + "duration": 1.56, + "entryType": "measure" + }, + { + "startTime": 12465.52, + "name": "lh:audit:clickjacking-mitigation", + "duration": 1.44, + "entryType": "measure" + }, + { + "startTime": 12467.26, + "name": "lh:audit:trusted-types-xss", + "duration": 1.49, + "entryType": "measure" + }, + { + "startTime": 12468.78, + "name": "lh:audit:script-treemap-data", + "duration": 149.07, + "entryType": "measure" + }, + { + "startTime": 12469.41, + "name": "lh:computed:ModuleDuplication", + "duration": 1.21, + "entryType": "measure" + }, + { + "startTime": 12470.72, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.41, + "entryType": "measure" + }, + { + "startTime": 12471.24, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 13.72, + "entryType": "measure" + }, + { + "startTime": 12485.98, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.59, + "entryType": "measure" + }, + { + "startTime": 12486.75, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.09, + "entryType": "measure" + }, + { + "startTime": 12486.96, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 4.59, + "entryType": "measure" + }, + { + "startTime": 12491.72, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 12492.06, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 4.02, + "entryType": "measure" + }, + { + "startTime": 12496.54, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 12496.94, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 24.72, + "entryType": "measure" + }, + { + "startTime": 12521.87, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.68, + "entryType": "measure" + }, + { + "startTime": 12523.79, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 12524.24, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.27, + "entryType": "measure" + }, + { + "startTime": 12525.01, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.86, + "entryType": "measure" + }, + { + "startTime": 12526.17, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 27.32, + "entryType": "measure" + }, + { + "startTime": 12553.81, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.53, + "entryType": "measure" + }, + { + "startTime": 12554.69, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.58, + "entryType": "measure" + }, + { + "startTime": 12555.48, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 12555.96, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.61, + "entryType": "measure" + }, + { + "startTime": 12556.93, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.63, + "entryType": "measure" + }, + { + "startTime": 12557.91, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.4, + "entryType": "measure" + }, + { + "startTime": 12558.72, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.71, + "entryType": "measure" + }, + { + "startTime": 12559.84, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.43, + "entryType": "measure" + }, + { + "startTime": 12560.53, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 12561.13, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.63, + "entryType": "measure" + }, + { + "startTime": 12562.25, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.58, + "entryType": "measure" + }, + { + "startTime": 12563.4, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.58, + "entryType": "measure" + }, + { + "startTime": 12564.5, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 6.59, + "entryType": "measure" + }, + { + "startTime": 12572.27, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.11, + "entryType": "measure" + }, + { + "startTime": 12573.91, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.54, + "entryType": "measure" + }, + { + "startTime": 12574.78, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 12575.68, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.57, + "entryType": "measure" + }, + { + "startTime": 12576.63, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.51, + "entryType": "measure" + }, + { + "startTime": 12577.56, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.59, + "entryType": "measure" + }, + { + "startTime": 12578.65, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.45, + "entryType": "measure" + }, + { + "startTime": 12579.59, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.53, + "entryType": "measure" + }, + { + "startTime": 12580.39, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 12580.91, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.42, + "entryType": "measure" + }, + { + "startTime": 12581.57, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 12582.01, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 12582.56, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.66, + "entryType": "measure" + }, + { + "startTime": 12583.63, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.52, + "entryType": "measure" + }, + { + "startTime": 12584.38, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 12584.88, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.35, + "entryType": "measure" + }, + { + "startTime": 12585.57, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.42, + "entryType": "measure" + }, + { + "startTime": 12586.22, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 12586.76, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.24, + "entryType": "measure" + }, + { + "startTime": 12588.27, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 12588.82, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.53, + "entryType": "measure" + }, + { + "startTime": 12589.6, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.25, + "entryType": "measure" + }, + { + "startTime": 12590.21, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.2, + "entryType": "measure" + }, + { + "startTime": 12591.66, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 12592.23, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.9, + "entryType": "measure" + }, + { + "startTime": 12593.55, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.66, + "entryType": "measure" + }, + { + "startTime": 12594.43, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.23, + "entryType": "measure" + }, + { + "startTime": 12594.98, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.46, + "entryType": "measure" + }, + { + "startTime": 12595.79, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.74, + "entryType": "measure" + }, + { + "startTime": 12596.82, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 12597.09, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.23, + "entryType": "measure" + }, + { + "startTime": 12597.5, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.64, + "entryType": "measure" + }, + { + "startTime": 12599.26, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 12599.53, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 12599.92, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.75, + "entryType": "measure" + }, + { + "startTime": 12600.86, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.34, + "entryType": "measure" + }, + { + "startTime": 12601.39, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.33, + "entryType": "measure" + }, + { + "startTime": 12601.9, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.58, + "entryType": "measure" + }, + { + "startTime": 12602.66, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.61, + "entryType": "measure" + }, + { + "startTime": 12603.48, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 12603.81, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 12604.1, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 12604.61, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.2, + "entryType": "measure" + }, + { + "startTime": 12605.03, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 12605.44, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.44, + "entryType": "measure" + }, + { + "startTime": 12606.1, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.5, + "entryType": "measure" + }, + { + "startTime": 12606.81, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.4, + "entryType": "measure" + }, + { + "startTime": 12607.34, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 12607.56, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.13, + "entryType": "measure" + }, + { + "startTime": 12607.89, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.25, + "entryType": "measure" + }, + { + "startTime": 12608.3, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 12608.53, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 12608.89, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.66, + "entryType": "measure" + }, + { + "startTime": 12609.78, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.29, + "entryType": "measure" + }, + { + "startTime": 12610.31, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 12610.69, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.13, + "entryType": "measure" + }, + { + "startTime": 12611.02, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.43, + "entryType": "measure" + }, + { + "startTime": 12611.67, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.52, + "entryType": "measure" + }, + { + "startTime": 12612.42, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.1, + "entryType": "measure" + }, + { + "startTime": 12613.66, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.14, + "entryType": "measure" + }, + { + "startTime": 12615.15, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.46, + "entryType": "measure" + }, + { + "startTime": 12615.79, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.14, + "entryType": "measure" + }, + { + "startTime": 12616.15, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.42, + "entryType": "measure" + }, + { + "startTime": 12616.82, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.29, + "entryType": "measure" + }, + { + "startTime": 12617.37, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.33, + "entryType": "measure" + }, + { + "startTime": 12618.3, + "name": "lh:audit:accesskeys", + "duration": 1.5, + "entryType": "measure" + }, + { + "startTime": 12620.2, + "name": "lh:audit:aria-allowed-attr", + "duration": 4.01, + "entryType": "measure" + }, + { + "startTime": 12624.71, + "name": "lh:audit:aria-allowed-role", + "duration": 4.59, + "entryType": "measure" + }, + { + "startTime": 12629.66, + "name": "lh:audit:aria-command-name", + "duration": 1.28, + "entryType": "measure" + }, + { + "startTime": 12631.29, + "name": "lh:audit:aria-conditional-attr", + "duration": 4.28, + "entryType": "measure" + }, + { + "startTime": 12635.89, + "name": "lh:audit:aria-deprecated-role", + "duration": 3.99, + "entryType": "measure" + }, + { + "startTime": 12640.23, + "name": "lh:audit:aria-dialog-name", + "duration": 1.36, + "entryType": "measure" + }, + { + "startTime": 12641.89, + "name": "lh:audit:aria-hidden-body", + "duration": 3.81, + "entryType": "measure" + }, + { + "startTime": 12646.03, + "name": "lh:audit:aria-hidden-focus", + "duration": 4.18, + "entryType": "measure" + }, + { + "startTime": 12650.68, + "name": "lh:audit:aria-input-field-name", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 12652.63, + "name": "lh:audit:aria-meter-name", + "duration": 1.53, + "entryType": "measure" + }, + { + "startTime": 12654.48, + "name": "lh:audit:aria-progressbar-name", + "duration": 1.58, + "entryType": "measure" + }, + { + "startTime": 12656.37, + "name": "lh:audit:aria-prohibited-attr", + "duration": 4.22, + "entryType": "measure" + }, + { + "startTime": 12660.89, + "name": "lh:audit:aria-required-attr", + "duration": 3.78, + "entryType": "measure" + }, + { + "startTime": 12664.98, + "name": "lh:audit:aria-required-children", + "duration": 1.67, + "entryType": "measure" + }, + { + "startTime": 12666.94, + "name": "lh:audit:aria-required-parent", + "duration": 1.69, + "entryType": "measure" + }, + { + "startTime": 12668.93, + "name": "lh:audit:aria-roles", + "duration": 7.49, + "entryType": "measure" + }, + { + "startTime": 12676.76, + "name": "lh:audit:aria-text", + "duration": 1.82, + "entryType": "measure" + }, + { + "startTime": 12678.88, + "name": "lh:audit:aria-toggle-field-name", + "duration": 1.82, + "entryType": "measure" + }, + { + "startTime": 12680.99, + "name": "lh:audit:aria-tooltip-name", + "duration": 1.91, + "entryType": "measure" + }, + { + "startTime": 12683.22, + "name": "lh:audit:aria-treeitem-name", + "duration": 2.09, + "entryType": "measure" + }, + { + "startTime": 12685.6, + "name": "lh:audit:aria-valid-attr-value", + "duration": 4.1, + "entryType": "measure" + }, + { + "startTime": 12690.02, + "name": "lh:audit:aria-valid-attr", + "duration": 3.52, + "entryType": "measure" + }, + { + "startTime": 12693.89, + "name": "lh:audit:button-name", + "duration": 3.8, + "entryType": "measure" + }, + { + "startTime": 12698.01, + "name": "lh:audit:bypass", + "duration": 4.17, + "entryType": "measure" + }, + { + "startTime": 12702.49, + "name": "lh:audit:color-contrast", + "duration": 4.1, + "entryType": "measure" + }, + { + "startTime": 12706.93, + "name": "lh:audit:definition-list", + "duration": 8.76, + "entryType": "measure" + }, + { + "startTime": 12716.02, + "name": "lh:audit:dlitem", + "duration": 2.11, + "entryType": "measure" + }, + { + "startTime": 12718.45, + "name": "lh:audit:document-title", + "duration": 4.8, + "entryType": "measure" + }, + { + "startTime": 12723.7, + "name": "lh:audit:duplicate-id-aria", + "duration": 4.23, + "entryType": "measure" + }, + { + "startTime": 12728.24, + "name": "lh:audit:empty-heading", + "duration": 3.43, + "entryType": "measure" + }, + { + "startTime": 12731.96, + "name": "lh:audit:form-field-multiple-labels", + "duration": 3.51, + "entryType": "measure" + }, + { + "startTime": 12735.77, + "name": "lh:audit:frame-title", + "duration": 2.22, + "entryType": "measure" + }, + { + "startTime": 12738.29, + "name": "lh:audit:heading-order", + "duration": 3.55, + "entryType": "measure" + }, + { + "startTime": 12742.16, + "name": "lh:audit:html-has-lang", + "duration": 4.09, + "entryType": "measure" + }, + { + "startTime": 12746.66, + "name": "lh:audit:html-lang-valid", + "duration": 3.63, + "entryType": "measure" + }, + { + "startTime": 12750.67, + "name": "lh:audit:html-xml-lang-mismatch", + "duration": 2.59, + "entryType": "measure" + }, + { + "startTime": 12753.58, + "name": "lh:audit:identical-links-same-purpose", + "duration": 8.88, + "entryType": "measure" + }, + { + "startTime": 12762.79, + "name": "lh:audit:image-alt", + "duration": 3.48, + "entryType": "measure" + }, + { + "startTime": 12766.58, + "name": "lh:audit:image-redundant-alt", + "duration": 3.69, + "entryType": "measure" + }, + { + "startTime": 12770.62, + "name": "lh:audit:input-button-name", + "duration": 2.25, + "entryType": "measure" + }, + { + "startTime": 12773.16, + "name": "lh:audit:input-image-alt", + "duration": 2.47, + "entryType": "measure" + }, + { + "startTime": 12775.98, + "name": "lh:audit:label-content-name-mismatch", + "duration": 3.53, + "entryType": "measure" + }, + { + "startTime": 12779.8, + "name": "lh:audit:label", + "duration": 3.45, + "entryType": "measure" + }, + { + "startTime": 12783.53, + "name": "lh:audit:landmark-one-main", + "duration": 3.73, + "entryType": "measure" + }, + { + "startTime": 12787.57, + "name": "lh:audit:link-name", + "duration": 3.58, + "entryType": "measure" + }, + { + "startTime": 12791.48, + "name": "lh:audit:link-in-text-block", + "duration": 2.99, + "entryType": "measure" + }, + { + "startTime": 12794.79, + "name": "lh:audit:list", + "duration": 10.97, + "entryType": "measure" + }, + { + "startTime": 12806.13, + "name": "lh:audit:listitem", + "duration": 4.91, + "entryType": "measure" + }, + { + "startTime": 12811.38, + "name": "lh:audit:meta-refresh", + "duration": 2.74, + "entryType": "measure" + }, + { + "startTime": 12814.44, + "name": "lh:audit:meta-viewport", + "duration": 3.89, + "entryType": "measure" + }, + { + "startTime": 12818.64, + "name": "lh:audit:object-alt", + "duration": 3.33, + "entryType": "measure" + }, + { + "startTime": 12822.31, + "name": "lh:audit:select-name", + "duration": 4.24, + "entryType": "measure" + }, + { + "startTime": 12827.36, + "name": "lh:audit:skip-link", + "duration": 3.99, + "entryType": "measure" + }, + { + "startTime": 12831.89, + "name": "lh:audit:tabindex", + "duration": 3.27, + "entryType": "measure" + }, + { + "startTime": 12835.51, + "name": "lh:audit:table-duplicate-name", + "duration": 3.28, + "entryType": "measure" + }, + { + "startTime": 12839.13, + "name": "lh:audit:table-fake-caption", + "duration": 3.24, + "entryType": "measure" + }, + { + "startTime": 12842.66, + "name": "lh:audit:target-size", + "duration": 10.27, + "entryType": "measure" + }, + { + "startTime": 12853.3, + "name": "lh:audit:td-has-header", + "duration": 3.21, + "entryType": "measure" + }, + { + "startTime": 12856.84, + "name": "lh:audit:td-headers-attr", + "duration": 3.43, + "entryType": "measure" + }, + { + "startTime": 12860.59, + "name": "lh:audit:th-has-data-cells", + "duration": 3.43, + "entryType": "measure" + }, + { + "startTime": 12864.33, + "name": "lh:audit:valid-lang", + "duration": 3.35, + "entryType": "measure" + }, + { + "startTime": 12867.97, + "name": "lh:audit:video-caption", + "duration": 3.69, + "entryType": "measure" + }, + { + "startTime": 12871.69, + "name": "lh:audit:custom-controls-labels", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 12871.81, + "name": "lh:audit:custom-controls-roles", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 12871.84, + "name": "lh:audit:focus-traps", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 12871.87, + "name": "lh:audit:focusable-controls", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 12871.92, + "name": "lh:audit:interactive-element-affordance", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 12871.95, + "name": "lh:audit:logical-tab-order", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 12871.97, + "name": "lh:audit:managed-focus", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 12871.99, + "name": "lh:audit:offscreen-content-hidden", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 12872.01, + "name": "lh:audit:use-landmarks", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 12872.03, + "name": "lh:audit:visual-order-follows-dom", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 12872.39, + "name": "lh:audit:uses-long-cache-ttl", + "duration": 3.1, + "entryType": "measure" + }, + { + "startTime": 12875.82, + "name": "lh:audit:total-byte-weight", + "duration": 1.85, + "entryType": "measure" + }, + { + "startTime": 12877.94, + "name": "lh:audit:offscreen-images", + "duration": 6.12, + "entryType": "measure" + }, + { + "startTime": 12884.37, + "name": "lh:audit:render-blocking-resources", + "duration": 20.01, + "entryType": "measure" + }, + { + "startTime": 12884.96, + "name": "lh:computed:UnusedCSS", + "duration": 17.98, + "entryType": "measure" + }, + { + "startTime": 12903.03, + "name": "lh:computed:NavigationInsights", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 12903.25, + "name": "lh:computed:FirstContentfulPaint", + "duration": 0.08, + "entryType": "measure" + }, + { + "startTime": 12904.63, + "name": "lh:audit:unminified-css", + "duration": 33.99, + "entryType": "measure" + }, + { + "startTime": 12938.86, + "name": "lh:audit:unminified-javascript", + "duration": 182.19, + "entryType": "measure" + }, + { + "startTime": 13121.39, + "name": "lh:audit:unused-css-rules", + "duration": 6.93, + "entryType": "measure" + }, + { + "startTime": 13128.59, + "name": "lh:audit:unused-javascript", + "duration": 18.46, + "entryType": "measure" + }, + { + "startTime": 13147.34, + "name": "lh:audit:modern-image-formats", + "duration": 5.31, + "entryType": "measure" + }, + { + "startTime": 13152.9, + "name": "lh:audit:uses-optimized-images", + "duration": 4.88, + "entryType": "measure" + }, + { + "startTime": 13158.04, + "name": "lh:audit:uses-text-compression", + "duration": 5.78, + "entryType": "measure" + }, + { + "startTime": 13164.08, + "name": "lh:audit:uses-responsive-images", + "duration": 9.29, + "entryType": "measure" + }, + { + "startTime": 13173.66, + "name": "lh:audit:efficient-animated-content", + "duration": 6.36, + "entryType": "measure" + }, + { + "startTime": 13180.44, + "name": "lh:audit:duplicated-javascript", + "duration": 5.98, + "entryType": "measure" + }, + { + "startTime": 13186.72, + "name": "lh:audit:legacy-javascript", + "duration": 393.14, + "entryType": "measure" + }, + { + "startTime": 13580.22, + "name": "lh:audit:doctype", + "duration": 1.38, + "entryType": "measure" + }, + { + "startTime": 13581.94, + "name": "lh:audit:charset", + "duration": 1.42, + "entryType": "measure" + }, + { + "startTime": 13583.7, + "name": "lh:audit:dom-size", + "duration": 2.39, + "entryType": "measure" + }, + { + "startTime": 13586.49, + "name": "lh:audit:geolocation-on-start", + "duration": 1.95, + "entryType": "measure" + }, + { + "startTime": 13588.78, + "name": "lh:audit:inspector-issues", + "duration": 1.13, + "entryType": "measure" + }, + { + "startTime": 13590.26, + "name": "lh:audit:no-document-write", + "duration": 1.87, + "entryType": "measure" + }, + { + "startTime": 13592.46, + "name": "lh:audit:js-libraries", + "duration": 1.16, + "entryType": "measure" + }, + { + "startTime": 13593.96, + "name": "lh:audit:notification-on-start", + "duration": 1.16, + "entryType": "measure" + }, + { + "startTime": 13595.57, + "name": "lh:audit:paste-preventing-inputs", + "duration": 3.15, + "entryType": "measure" + }, + { + "startTime": 13598.99, + "name": "lh:audit:uses-http2", + "duration": 7.65, + "entryType": "measure" + }, + { + "startTime": 13606.99, + "name": "lh:audit:uses-passive-event-listeners", + "duration": 1.23, + "entryType": "measure" + }, + { + "startTime": 13608.53, + "name": "lh:audit:meta-description", + "duration": 1.1, + "entryType": "measure" + }, + { + "startTime": 13609.95, + "name": "lh:audit:http-status-code", + "duration": 1.05, + "entryType": "measure" + }, + { + "startTime": 13611.34, + "name": "lh:audit:font-size", + "duration": 1.19, + "entryType": "measure" + }, + { + "startTime": 13612.84, + "name": "lh:audit:link-text", + "duration": 2.05, + "entryType": "measure" + }, + { + "startTime": 13615.23, + "name": "lh:audit:crawlable-anchors", + "duration": 1.46, + "entryType": "measure" + }, + { + "startTime": 13617.03, + "name": "lh:audit:is-crawlable", + "duration": 4.04, + "entryType": "measure" + }, + { + "startTime": 13621.82, + "name": "lh:audit:robots-txt", + "duration": 4.79, + "entryType": "measure" + }, + { + "startTime": 13627.32, + "name": "lh:audit:hreflang", + "duration": 1.49, + "entryType": "measure" + }, + { + "startTime": 13629.12, + "name": "lh:audit:canonical", + "duration": 1.23, + "entryType": "measure" + }, + { + "startTime": 13630.6, + "name": "lh:audit:structured-data", + "duration": 0.68, + "entryType": "measure" + }, + { + "startTime": 13631.66, + "name": "lh:audit:bf-cache", + "duration": 1.48, + "entryType": "measure" + }, + { + "startTime": 13633.46, + "name": "lh:audit:cache-insight", + "duration": 1.53, + "entryType": "measure" + }, + { + "startTime": 13635.32, + "name": "lh:audit:cls-culprits-insight", + "duration": 2.02, + "entryType": "measure" + }, + { + "startTime": 13637.65, + "name": "lh:audit:document-latency-insight", + "duration": 1.68, + "entryType": "measure" + }, + { + "startTime": 13639.75, + "name": "lh:audit:dom-size-insight", + "duration": 1.6, + "entryType": "measure" + }, + { + "startTime": 13641.66, + "name": "lh:audit:duplicated-javascript-insight", + "duration": 1.21, + "entryType": "measure" + }, + { + "startTime": 13643.21, + "name": "lh:audit:font-display-insight", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 13644.85, + "name": "lh:audit:forced-reflow-insight", + "duration": 1.87, + "entryType": "measure" + }, + { + "startTime": 13647.05, + "name": "lh:audit:image-delivery-insight", + "duration": 1.44, + "entryType": "measure" + }, + { + "startTime": 13648.81, + "name": "lh:audit:inp-breakdown-insight", + "duration": 1.29, + "entryType": "measure" + }, + { + "startTime": 13650.42, + "name": "lh:audit:lcp-breakdown-insight", + "duration": 1.44, + "entryType": "measure" + }, + { + "startTime": 13652.16, + "name": "lh:audit:lcp-discovery-insight", + "duration": 1.46, + "entryType": "measure" + }, + { + "startTime": 13654.4, + "name": "lh:audit:legacy-javascript-insight", + "duration": 1.67, + "entryType": "measure" + }, + { + "startTime": 13656.64, + "name": "lh:audit:modern-http-insight", + "duration": 1.26, + "entryType": "measure" + }, + { + "startTime": 13658.3, + "name": "lh:audit:network-dependency-tree-insight", + "duration": 2.2, + "entryType": "measure" + }, + { + "startTime": 13660.85, + "name": "lh:audit:render-blocking-insight", + "duration": 1.29, + "entryType": "measure" + }, + { + "startTime": 13666.66, + "name": "lh:audit:third-parties-insight", + "duration": 14.05, + "entryType": "measure" + }, + { + "startTime": 13681.25, + "name": "lh:audit:viewport-insight", + "duration": 1.26, + "entryType": "measure" + }, + { + "startTime": 13682.55, + "name": "lh:runner:generate", + "duration": 0.61, + "entryType": "measure" + } + ], + "total": 11389.11 + }, + "i18n": { + "rendererFormattedStrings": { + "calculatorLink": "See calculator.", + "collapseView": "Collapse view", + "crcInitialNavigation": "Initial Navigation", + "crcLongestDurationLabel": "Maximum critical path latency:", + "dropdownCopyJSON": "Copy JSON", + "dropdownDarkTheme": "Toggle Dark Theme", + "dropdownPrintExpanded": "Print Expanded", + "dropdownPrintSummary": "Print Summary", + "dropdownSaveGist": "Save as Gist", + "dropdownSaveHTML": "Save as HTML", + "dropdownSaveJSON": "Save as JSON", + "dropdownViewUnthrottledTrace": "View Unthrottled Trace", + "dropdownViewer": "Open in Viewer", + "errorLabel": "Error!", + "errorMissingAuditInfo": "Report error: no audit information", + "expandView": "Expand view", + "firstPartyChipLabel": "1st party", + "footerIssue": "File an issue", + "goBackToAudits": "Go back to audits", + "hide": "Hide", + "insightsNotice": "Later this year, insights will replace performance audits. [Learn more and provide feedback here](https://github.com/GoogleChrome/lighthouse/discussions/16462).", + "labDataTitle": "Lab Data", + "lsPerformanceCategoryDescription": "[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.", + "manualAuditsGroupTitle": "Additional items to manually check", + "notApplicableAuditsGroupTitle": "Not applicable", + "openInANewTabTooltip": "Open in a new tab", + "opportunityResourceColumnLabel": "Opportunity", + "opportunitySavingsColumnLabel": "Estimated Savings", + "passedAuditsGroupTitle": "Passed audits", + "runtimeAnalysisWindow": "Initial page load", + "runtimeAnalysisWindowSnapshot": "Point-in-time snapshot", + "runtimeAnalysisWindowTimespan": "User interactions timespan", + "runtimeCustom": "Custom throttling", + "runtimeDesktopEmulation": "Emulated Desktop", + "runtimeMobileEmulation": "Emulated Moto G Power", + "runtimeNoEmulation": "No emulation", + "runtimeSettingsAxeVersion": "Axe version", + "runtimeSettingsBenchmark": "Unthrottled CPU/Memory Power", + "runtimeSettingsCPUThrottling": "CPU throttling", + "runtimeSettingsDevice": "Device", + "runtimeSettingsNetworkThrottling": "Network throttling", + "runtimeSettingsScreenEmulation": "Screen emulation", + "runtimeSettingsUANetwork": "User agent (network)", + "runtimeSingleLoad": "Single page session", + "runtimeSingleLoadTooltip": "This data is taken from a single page session, as opposed to field data summarizing many sessions.", + "runtimeSlow4g": "Slow 4G throttling", + "runtimeUnknown": "Unknown", + "show": "Show", + "showRelevantAudits": "Show audits relevant to:", + "snippetCollapseButtonLabel": "Collapse snippet", + "snippetExpandButtonLabel": "Expand snippet", + "thirdPartyResourcesLabel": "Show 3rd-party resources", + "throttlingProvided": "Provided by environment", + "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", + "tryInsights": "Try insights", + "unattributable": "Unattributable", + "varianceDisclaimer": "Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.", + "viewTraceLabel": "View Trace", + "viewTreemapLabel": "View Treemap", + "warningAuditsGroupTitle": "Passed audits but with warnings", + "warningHeader": "Warnings: " + }, + "icuMessagePaths": { + "core/audits/is-on-https.js | title": ["audits[is-on-https].title"], + "core/audits/is-on-https.js | description": [ + "audits[is-on-https].description" + ], + "core/audits/is-on-https.js | columnInsecureURL": [ + "audits[is-on-https].details.headings[0].label" + ], + "core/audits/is-on-https.js | columnResolution": [ + "audits[is-on-https].details.headings[1].label" + ], + "core/audits/redirects-http.js | title": ["audits[redirects-http].title"], + "core/audits/redirects-http.js | description": [ + "audits[redirects-http].description" + ], + "core/audits/viewport.js | title": ["audits.viewport.title"], + "core/audits/viewport.js | description": ["audits.viewport.description"], + "core/lib/i18n/i18n.js | firstContentfulPaintMetric": [ + "audits[first-contentful-paint].title" + ], + "core/audits/metrics/first-contentful-paint.js | description": [ + "audits[first-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | seconds": [ + { + "values": { + "timeInMs": 1876.01325 + }, + "path": "audits[first-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 3136.6887500000003 + }, + "path": "audits[largest-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 1876.01325 + }, + "path": "audits[speed-index].displayValue" + }, + { + "values": { + "timeInMs": 3136.6887500000003 + }, + "path": "audits.interactive.displayValue" + }, + { + "values": { + "timeInMs": 636.516999999995 + }, + "path": "audits[mainthread-work-breakdown].displayValue" + }, + { + "values": { + "timeInMs": 160.60099999999954 + }, + "path": "audits[bootup-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | largestContentfulPaintMetric": [ + "audits[largest-contentful-paint].title" + ], + "core/audits/metrics/largest-contentful-paint.js | description": [ + "audits[largest-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | firstMeaningfulPaintMetric": [ + "audits[first-meaningful-paint].title" + ], + "core/audits/metrics/first-meaningful-paint.js | description": [ + "audits[first-meaningful-paint].description" + ], + "core/lib/i18n/i18n.js | speedIndexMetric": ["audits[speed-index].title"], + "core/audits/metrics/speed-index.js | description": [ + "audits[speed-index].description" + ], + "core/lib/i18n/i18n.js | totalBlockingTimeMetric": [ + "audits[total-blocking-time].title" + ], + "core/audits/metrics/total-blocking-time.js | description": [ + "audits[total-blocking-time].description" + ], + "core/lib/i18n/i18n.js | ms": [ + { + "values": { + "timeInMs": 0 + }, + "path": "audits[total-blocking-time].displayValue" + }, + { + "values": { + "timeInMs": 42 + }, + "path": "audits[max-potential-fid].displayValue" + }, + { + "values": { + "timeInMs": 0.05925 + }, + "path": "audits[network-rtt].displayValue" + }, + { + "values": { + "timeInMs": 3.33775 + }, + "path": "audits[network-server-latency].displayValue" + }, + { + "values": { + "timeInMs": 3136.6887500000003 + }, + "path": "audits[largest-contentful-paint-element].displayValue" + } + ], + "core/lib/i18n/i18n.js | maxPotentialFIDMetric": [ + "audits[max-potential-fid].title" + ], + "core/audits/metrics/max-potential-fid.js | description": [ + "audits[max-potential-fid].description" + ], + "core/lib/i18n/i18n.js | cumulativeLayoutShiftMetric": [ + "audits[cumulative-layout-shift].title" + ], + "core/audits/metrics/cumulative-layout-shift.js | description": [ + "audits[cumulative-layout-shift].description" + ], + "core/audits/errors-in-console.js | failureTitle": [ + "audits[errors-in-console].title" + ], + "core/audits/errors-in-console.js | description": [ + "audits[errors-in-console].description" + ], + "core/lib/i18n/i18n.js | columnSource": [ + "audits[errors-in-console].details.headings[0].label", + "audits.deprecations.details.headings[1].label", + "audits[geolocation-on-start].details.headings[0].label", + "audits[no-document-write].details.headings[0].label", + "audits[notification-on-start].details.headings[0].label", + "audits[uses-passive-event-listeners].details.headings[0].label", + "audits[forced-reflow-insight].details.items[0].headings[0].label" + ], + "core/lib/i18n/i18n.js | columnDescription": [ + "audits[errors-in-console].details.headings[1].label", + "audits[csp-xss].details.headings[0].label", + "audits[has-hsts].details.headings[0].label", + "audits[origin-isolation].details.headings[0].label", + "audits[clickjacking-mitigation].details.headings[0].label", + "audits[trusted-types-xss].details.headings[0].label" + ], + "core/audits/server-response-time.js | title": [ + "audits[server-response-time].title" + ], + "core/audits/server-response-time.js | description": [ + "audits[server-response-time].description" + ], + "core/audits/server-response-time.js | displayValue": [ + { + "values": { + "timeInMs": 9.386 + }, + "path": "audits[server-response-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnURL": [ + "audits[server-response-time].details.headings[0].label", + "audits[image-aspect-ratio].details.headings[1].label", + "audits[image-size-responsive].details.headings[1].label", + "audits[third-party-cookies].details.headings[1].label", + "audits[bootup-time].details.headings[0].label", + "audits[font-display].details.headings[0].label", + "audits[network-rtt].details.headings[0].label", + "audits[network-server-latency].details.headings[0].label", + "audits[long-tasks].details.headings[0].label", + "audits[unsized-images].details.headings[1].label", + "audits[valid-source-maps].details.headings[0].label", + "audits[uses-long-cache-ttl].details.headings[0].label", + "audits[total-byte-weight].details.headings[0].label", + "audits[unminified-javascript].details.headings[0].label", + "audits[unused-javascript].details.headings[0].label", + "audits[uses-text-compression].details.headings[0].label", + "audits[font-display-insight].details.headings[0].label", + "audits[image-delivery-insight].details.headings[0].label", + "audits[legacy-javascript-insight].details.headings[0].label", + "audits[modern-http-insight].details.headings[0].label", + "audits[render-blocking-insight].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnTimeSpent": [ + "audits[server-response-time].details.headings[1].label", + "audits[mainthread-work-breakdown].details.headings[1].label", + "audits[network-rtt].details.headings[1].label", + "audits[network-server-latency].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | interactiveMetric": ["audits.interactive.title"], + "core/audits/metrics/interactive.js | description": [ + "audits.interactive.description" + ], + "core/audits/user-timings.js | title": ["audits[user-timings].title"], + "core/audits/user-timings.js | description": [ + "audits[user-timings].description" + ], + "core/audits/user-timings.js | displayValue": [ + { + "values": { + "itemCount": 15 + }, + "path": "audits[user-timings].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnName": [ + "audits[user-timings].details.headings[0].label", + "audits[third-party-cookies].details.headings[0].label" + ], + "core/audits/user-timings.js | columnType": [ + "audits[user-timings].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnStartTime": [ + "audits[user-timings].details.headings[2].label", + "audits[long-tasks].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnDuration": [ + "audits[user-timings].details.headings[3].label", + "audits[long-tasks].details.headings[2].label", + "audits[lcp-breakdown-insight].details.items[0].headings[1].label", + "audits[render-blocking-insight].details.headings[2].label" + ], + "core/audits/critical-request-chains.js | title": [ + "audits[critical-request-chains].title" + ], + "core/audits/critical-request-chains.js | description": [ + "audits[critical-request-chains].description" + ], + "core/audits/critical-request-chains.js | displayValue": [ + { + "values": { + "itemCount": 54 + }, + "path": "audits[critical-request-chains].displayValue" + } + ], + "core/audits/redirects.js | title": ["audits.redirects.title"], + "core/audits/redirects.js | description": [ + "audits.redirects.description" + ], + "core/audits/image-aspect-ratio.js | title": [ + "audits[image-aspect-ratio].title" + ], + "core/audits/image-aspect-ratio.js | description": [ + "audits[image-aspect-ratio].description" + ], + "core/audits/image-aspect-ratio.js | columnDisplayed": [ + "audits[image-aspect-ratio].details.headings[2].label" + ], + "core/audits/image-aspect-ratio.js | columnActual": [ + "audits[image-aspect-ratio].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | title": [ + "audits[image-size-responsive].title" + ], + "core/audits/image-size-responsive.js | description": [ + "audits[image-size-responsive].description" + ], + "core/audits/image-size-responsive.js | columnDisplayed": [ + "audits[image-size-responsive].details.headings[2].label" + ], + "core/audits/image-size-responsive.js | columnActual": [ + "audits[image-size-responsive].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | columnExpected": [ + "audits[image-size-responsive].details.headings[4].label" + ], + "core/audits/deprecations.js | title": ["audits.deprecations.title"], + "core/audits/deprecations.js | description": [ + "audits.deprecations.description" + ], + "core/audits/deprecations.js | columnDeprecate": [ + "audits.deprecations.details.headings[0].label" + ], + "core/audits/third-party-cookies.js | title": [ + "audits[third-party-cookies].title" + ], + "core/audits/third-party-cookies.js | description": [ + "audits[third-party-cookies].description" + ], + "core/audits/mainthread-work-breakdown.js | title": [ + "audits[mainthread-work-breakdown].title" + ], + "core/audits/mainthread-work-breakdown.js | description": [ + "audits[mainthread-work-breakdown].description" + ], + "core/audits/mainthread-work-breakdown.js | columnCategory": [ + "audits[mainthread-work-breakdown].details.headings[0].label" + ], + "core/audits/bootup-time.js | title": ["audits[bootup-time].title"], + "core/audits/bootup-time.js | description": [ + "audits[bootup-time].description" + ], + "core/audits/bootup-time.js | columnTotal": [ + "audits[bootup-time].details.headings[1].label" + ], + "core/audits/bootup-time.js | columnScriptEval": [ + "audits[bootup-time].details.headings[2].label" + ], + "core/audits/bootup-time.js | columnScriptParse": [ + "audits[bootup-time].details.headings[3].label" + ], + "core/audits/uses-rel-preconnect.js | title": [ + "audits[uses-rel-preconnect].title" + ], + "core/audits/uses-rel-preconnect.js | description": [ + "audits[uses-rel-preconnect].description" + ], + "core/audits/font-display.js | title": ["audits[font-display].title"], + "core/audits/font-display.js | description": [ + "audits[font-display].description" + ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[font-display].details.headings[1].label", + "audits[unminified-javascript].details.headings[2].label", + "audits[unused-javascript].details.headings[2].label", + "audits[uses-text-compression].details.headings[2].label", + "audits[font-display-insight].details.headings[1].label", + "audits[image-delivery-insight].details.headings[2].label" + ], + "core/audits/network-rtt.js | title": ["audits[network-rtt].title"], + "core/audits/network-rtt.js | description": [ + "audits[network-rtt].description" + ], + "core/audits/network-server-latency.js | title": [ + "audits[network-server-latency].title" + ], + "core/audits/network-server-latency.js | description": [ + "audits[network-server-latency].description" + ], + "core/lib/i18n/i18n.js | columnResourceType": [ + "audits[resource-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnRequests": [ + "audits[resource-summary].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnTransferSize": [ + "audits[resource-summary].details.headings[2].label", + "audits[third-party-summary].details.headings[1].label", + "audits[uses-long-cache-ttl].details.headings[2].label", + "audits[total-byte-weight].details.headings[1].label", + "audits[unminified-javascript].details.headings[1].label", + "audits[unused-javascript].details.headings[1].label", + "audits[uses-text-compression].details.headings[1].label", + "audits[cache-insight].details.headings[2].label", + "audits[render-blocking-insight].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | total": [ + "audits[resource-summary].details.items[0].label", + "audits[cls-culprits-insight].details.items[0].items[0].node.value" + ], + "core/lib/i18n/i18n.js | scriptResourceType": [ + "audits[resource-summary].details.items[1].label" + ], + "core/lib/i18n/i18n.js | fontResourceType": [ + "audits[resource-summary].details.items[2].label" + ], + "core/lib/i18n/i18n.js | imageResourceType": [ + "audits[resource-summary].details.items[3].label" + ], + "core/lib/i18n/i18n.js | documentResourceType": [ + "audits[resource-summary].details.items[4].label" + ], + "core/lib/i18n/i18n.js | otherResourceType": [ + "audits[resource-summary].details.items[5].label" + ], + "core/lib/i18n/i18n.js | stylesheetResourceType": [ + "audits[resource-summary].details.items[6].label" + ], + "core/lib/i18n/i18n.js | mediaResourceType": [ + "audits[resource-summary].details.items[7].label" + ], + "core/lib/i18n/i18n.js | thirdPartyResourceType": [ + "audits[resource-summary].details.items[8].label" + ], + "core/audits/third-party-summary.js | title": [ + "audits[third-party-summary].title" + ], + "core/audits/third-party-summary.js | description": [ + "audits[third-party-summary].description" + ], + "core/audits/third-party-summary.js | displayValue": [ + { + "values": { + "timeInMs": 0 + }, + "path": "audits[third-party-summary].displayValue" + } + ], + "core/audits/third-party-summary.js | columnThirdParty": [ + "audits[third-party-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnBlockingTime": [ + "audits[third-party-summary].details.headings[2].label" + ], + "core/audits/third-party-facades.js | title": [ + "audits[third-party-facades].title" + ], + "core/audits/third-party-facades.js | description": [ + "audits[third-party-facades].description" + ], + "core/audits/largest-contentful-paint-element.js | title": [ + "audits[largest-contentful-paint-element].title" + ], + "core/audits/largest-contentful-paint-element.js | description": [ + "audits[largest-contentful-paint-element].description" + ], + "core/lib/i18n/i18n.js | columnElement": [ + "audits[largest-contentful-paint-element].details.items[0].headings[0].label", + "audits[layout-shifts].details.headings[0].label", + "audits[non-composited-animations].details.headings[0].label", + "audits[dom-size].details.headings[1].label", + "audits[cls-culprits-insight].details.items[0].headings[0].label", + "audits[dom-size-insight].details.headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPhase": [ + "audits[largest-contentful-paint-element].details.items[1].headings[0].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPercentOfLCP": [ + "audits[largest-contentful-paint-element].details.items[1].headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnTiming": [ + "audits[largest-contentful-paint-element].details.items[1].headings[2].label" + ], + "core/audits/largest-contentful-paint-element.js | itemTTFB": [ + "audits[largest-contentful-paint-element].details.items[1].items[0].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[1].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadTime": [ + "audits[largest-contentful-paint-element].details.items[1].items[2].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemRenderDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[3].phase" + ], + "core/audits/lcp-lazy-loaded.js | title": [ + "audits[lcp-lazy-loaded].title" + ], + "core/audits/lcp-lazy-loaded.js | description": [ + "audits[lcp-lazy-loaded].description" + ], + "core/audits/layout-shifts.js | title": ["audits[layout-shifts].title"], + "core/audits/layout-shifts.js | description": [ + "audits[layout-shifts].description" + ], + "core/audits/layout-shifts.js | displayValueShiftsFound": [ + { + "values": { + "shiftCount": 2 + }, + "path": "audits[layout-shifts].displayValue" + } + ], + "core/audits/layout-shifts.js | columnScore": [ + "audits[layout-shifts].details.headings[1].label" + ], + "core/audits/layout-shifts.js | rootCauseUnsizedMedia": [ + "audits[layout-shifts].details.items[1].subItems.items[0].cause" + ], + "core/audits/long-tasks.js | title": ["audits[long-tasks].title"], + "core/audits/long-tasks.js | description": [ + "audits[long-tasks].description" + ], + "core/audits/long-tasks.js | displayValue": [ + { + "values": { + "itemCount": 2 + }, + "path": "audits[long-tasks].displayValue" + } + ], + "core/audits/non-composited-animations.js | title": [ + "audits[non-composited-animations].title" + ], + "core/audits/non-composited-animations.js | description": [ + "audits[non-composited-animations].description" + ], + "core/audits/unsized-images.js | failureTitle": [ + "audits[unsized-images].title" + ], + "core/audits/unsized-images.js | description": [ + "audits[unsized-images].description" + ], + "core/audits/valid-source-maps.js | title": [ + "audits[valid-source-maps].title" + ], + "core/audits/valid-source-maps.js | description": [ + "audits[valid-source-maps].description" + ], + "core/audits/valid-source-maps.js | columnMapURL": [ + "audits[valid-source-maps].details.headings[1].label" + ], + "core/audits/prioritize-lcp-image.js | title": [ + "audits[prioritize-lcp-image].title" + ], + "core/audits/prioritize-lcp-image.js | description": [ + "audits[prioritize-lcp-image].description" + ], + "core/audits/csp-xss.js | title": ["audits[csp-xss].title"], + "core/audits/csp-xss.js | description": ["audits[csp-xss].description"], + "core/audits/csp-xss.js | columnDirective": [ + "audits[csp-xss].details.headings[1].label" + ], + "core/audits/csp-xss.js | columnSeverity": [ + "audits[csp-xss].details.headings[2].label" + ], + "core/lib/i18n/i18n.js | itemSeverityHigh": [ + "audits[csp-xss].details.items[0].severity", + "audits[has-hsts].details.items[0].severity", + "audits[origin-isolation].details.items[0].severity", + "audits[clickjacking-mitigation].details.items[0].severity", + "audits[trusted-types-xss].details.items[0].severity" + ], + "core/audits/csp-xss.js | noCsp": [ + "audits[csp-xss].details.items[0].description" + ], + "core/audits/has-hsts.js | title": ["audits[has-hsts].title"], + "core/audits/has-hsts.js | description": ["audits[has-hsts].description"], + "core/audits/has-hsts.js | columnDirective": [ + "audits[has-hsts].details.headings[1].label" + ], + "core/audits/has-hsts.js | columnSeverity": [ + "audits[has-hsts].details.headings[2].label" + ], + "core/audits/has-hsts.js | noHsts": [ + "audits[has-hsts].details.items[0].description" + ], + "core/audits/origin-isolation.js | title": [ + "audits[origin-isolation].title" + ], + "core/audits/origin-isolation.js | description": [ + "audits[origin-isolation].description" + ], + "core/audits/origin-isolation.js | columnDirective": [ + "audits[origin-isolation].details.headings[1].label" + ], + "core/audits/origin-isolation.js | columnSeverity": [ + "audits[origin-isolation].details.headings[2].label" + ], + "core/audits/origin-isolation.js | noCoop": [ + "audits[origin-isolation].details.items[0].description" + ], + "core/audits/clickjacking-mitigation.js | title": [ + "audits[clickjacking-mitigation].title" + ], + "core/audits/clickjacking-mitigation.js | description": [ + "audits[clickjacking-mitigation].description" + ], + "core/audits/clickjacking-mitigation.js | columnSeverity": [ + "audits[clickjacking-mitigation].details.headings[1].label" + ], + "core/audits/clickjacking-mitigation.js | noClickjackingMitigation": [ + "audits[clickjacking-mitigation].details.items[0].description" + ], + "core/audits/trusted-types-xss.js | title": [ + "audits[trusted-types-xss].title" + ], + "core/audits/trusted-types-xss.js | description": [ + "audits[trusted-types-xss].description" + ], + "core/audits/trusted-types-xss.js | columnSeverity": [ + "audits[trusted-types-xss].details.headings[1].label" + ], + "core/audits/trusted-types-xss.js | noTrustedTypesToMitigateXss": [ + "audits[trusted-types-xss].details.items[0].description" + ], + "core/audits/accessibility/accesskeys.js | title": [ + "audits.accesskeys.title" + ], + "core/audits/accessibility/accesskeys.js | description": [ + "audits.accesskeys.description" + ], + "core/audits/accessibility/aria-allowed-attr.js | title": [ + "audits[aria-allowed-attr].title" + ], + "core/audits/accessibility/aria-allowed-attr.js | description": [ + "audits[aria-allowed-attr].description" + ], + "core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[aria-allowed-attr].details.headings[0].label", + "audits[aria-allowed-role].details.headings[0].label", + "audits[aria-conditional-attr].details.headings[0].label", + "audits[aria-deprecated-role].details.headings[0].label", + "audits[aria-hidden-body].details.headings[0].label", + "audits[aria-hidden-focus].details.headings[0].label", + "audits[aria-prohibited-attr].details.headings[0].label", + "audits[aria-required-attr].details.headings[0].label", + "audits[aria-roles].details.headings[0].label", + "audits[aria-valid-attr-value].details.headings[0].label", + "audits[aria-valid-attr].details.headings[0].label", + "audits[button-name].details.headings[0].label", + "audits[color-contrast].details.headings[0].label", + "audits[document-title].details.headings[0].label", + "audits[heading-order].details.headings[0].label", + "audits[html-has-lang].details.headings[0].label", + "audits[html-lang-valid].details.headings[0].label", + "audits[image-alt].details.headings[0].label", + "audits[image-redundant-alt].details.headings[0].label", + "audits[label-content-name-mismatch].details.headings[0].label", + "audits.label.details.headings[0].label", + "audits[link-name].details.headings[0].label", + "audits.list.details.headings[0].label", + "audits.listitem.details.headings[0].label", + "audits[meta-viewport].details.headings[0].label", + "audits[skip-link].details.headings[0].label", + "audits[target-size].details.headings[0].label", + "audits[paste-preventing-inputs].details.headings[0].label" + ], + "core/audits/accessibility/aria-allowed-role.js | title": [ + "audits[aria-allowed-role].title" + ], + "core/audits/accessibility/aria-allowed-role.js | description": [ + "audits[aria-allowed-role].description" + ], + "core/audits/accessibility/aria-command-name.js | title": [ + "audits[aria-command-name].title" + ], + "core/audits/accessibility/aria-command-name.js | description": [ + "audits[aria-command-name].description" + ], + "core/audits/accessibility/aria-conditional-attr.js | title": [ + "audits[aria-conditional-attr].title" + ], + "core/audits/accessibility/aria-conditional-attr.js | description": [ + "audits[aria-conditional-attr].description" + ], + "core/audits/accessibility/aria-deprecated-role.js | title": [ + "audits[aria-deprecated-role].title" + ], + "core/audits/accessibility/aria-deprecated-role.js | description": [ + "audits[aria-deprecated-role].description" + ], + "core/audits/accessibility/aria-dialog-name.js | title": [ + "audits[aria-dialog-name].title" + ], + "core/audits/accessibility/aria-dialog-name.js | description": [ + "audits[aria-dialog-name].description" + ], + "core/audits/accessibility/aria-hidden-body.js | title": [ + "audits[aria-hidden-body].title" + ], + "core/audits/accessibility/aria-hidden-body.js | description": [ + "audits[aria-hidden-body].description" + ], + "core/audits/accessibility/aria-hidden-focus.js | title": [ + "audits[aria-hidden-focus].title" + ], + "core/audits/accessibility/aria-hidden-focus.js | description": [ + "audits[aria-hidden-focus].description" + ], + "core/audits/accessibility/aria-input-field-name.js | title": [ + "audits[aria-input-field-name].title" + ], + "core/audits/accessibility/aria-input-field-name.js | description": [ + "audits[aria-input-field-name].description" + ], + "core/audits/accessibility/aria-meter-name.js | title": [ + "audits[aria-meter-name].title" + ], + "core/audits/accessibility/aria-meter-name.js | description": [ + "audits[aria-meter-name].description" + ], + "core/audits/accessibility/aria-progressbar-name.js | title": [ + "audits[aria-progressbar-name].title" + ], + "core/audits/accessibility/aria-progressbar-name.js | description": [ + "audits[aria-progressbar-name].description" + ], + "core/audits/accessibility/aria-prohibited-attr.js | title": [ + "audits[aria-prohibited-attr].title" + ], + "core/audits/accessibility/aria-prohibited-attr.js | description": [ + "audits[aria-prohibited-attr].description" + ], + "core/audits/accessibility/aria-required-attr.js | title": [ + "audits[aria-required-attr].title" + ], + "core/audits/accessibility/aria-required-attr.js | description": [ + "audits[aria-required-attr].description" + ], + "core/audits/accessibility/aria-required-children.js | title": [ + "audits[aria-required-children].title" + ], + "core/audits/accessibility/aria-required-children.js | description": [ + "audits[aria-required-children].description" + ], + "core/audits/accessibility/aria-required-parent.js | title": [ + "audits[aria-required-parent].title" + ], + "core/audits/accessibility/aria-required-parent.js | description": [ + "audits[aria-required-parent].description" + ], + "core/audits/accessibility/aria-roles.js | title": [ + "audits[aria-roles].title" + ], + "core/audits/accessibility/aria-roles.js | description": [ + "audits[aria-roles].description" + ], + "core/audits/accessibility/aria-text.js | title": [ + "audits[aria-text].title" + ], + "core/audits/accessibility/aria-text.js | description": [ + "audits[aria-text].description" + ], + "core/audits/accessibility/aria-toggle-field-name.js | title": [ + "audits[aria-toggle-field-name].title" + ], + "core/audits/accessibility/aria-toggle-field-name.js | description": [ + "audits[aria-toggle-field-name].description" + ], + "core/audits/accessibility/aria-tooltip-name.js | title": [ + "audits[aria-tooltip-name].title" + ], + "core/audits/accessibility/aria-tooltip-name.js | description": [ + "audits[aria-tooltip-name].description" + ], + "core/audits/accessibility/aria-treeitem-name.js | title": [ + "audits[aria-treeitem-name].title" + ], + "core/audits/accessibility/aria-treeitem-name.js | description": [ + "audits[aria-treeitem-name].description" + ], + "core/audits/accessibility/aria-valid-attr-value.js | title": [ + "audits[aria-valid-attr-value].title" + ], + "core/audits/accessibility/aria-valid-attr-value.js | description": [ + "audits[aria-valid-attr-value].description" + ], + "core/audits/accessibility/aria-valid-attr.js | title": [ + "audits[aria-valid-attr].title" + ], + "core/audits/accessibility/aria-valid-attr.js | description": [ + "audits[aria-valid-attr].description" + ], + "core/audits/accessibility/button-name.js | title": [ + "audits[button-name].title" + ], + "core/audits/accessibility/button-name.js | description": [ + "audits[button-name].description" + ], + "core/audits/accessibility/bypass.js | title": ["audits.bypass.title"], + "core/audits/accessibility/bypass.js | description": [ + "audits.bypass.description" + ], + "core/audits/accessibility/color-contrast.js | title": [ + "audits[color-contrast].title" + ], + "core/audits/accessibility/color-contrast.js | description": [ + "audits[color-contrast].description" + ], + "core/audits/accessibility/definition-list.js | title": [ + "audits[definition-list].title" + ], + "core/audits/accessibility/definition-list.js | description": [ + "audits[definition-list].description" + ], + "core/audits/accessibility/dlitem.js | title": ["audits.dlitem.title"], + "core/audits/accessibility/dlitem.js | description": [ + "audits.dlitem.description" + ], + "core/audits/accessibility/document-title.js | title": [ + "audits[document-title].title" + ], + "core/audits/accessibility/document-title.js | description": [ + "audits[document-title].description" + ], + "core/audits/accessibility/duplicate-id-aria.js | title": [ + "audits[duplicate-id-aria].title" + ], + "core/audits/accessibility/duplicate-id-aria.js | description": [ + "audits[duplicate-id-aria].description" + ], + "core/audits/accessibility/empty-heading.js | title": [ + "audits[empty-heading].title" + ], + "core/audits/accessibility/empty-heading.js | description": [ + "audits[empty-heading].description" + ], + "core/audits/accessibility/form-field-multiple-labels.js | title": [ + "audits[form-field-multiple-labels].title" + ], + "core/audits/accessibility/form-field-multiple-labels.js | description": [ + "audits[form-field-multiple-labels].description" + ], + "core/audits/accessibility/frame-title.js | title": [ + "audits[frame-title].title" + ], + "core/audits/accessibility/frame-title.js | description": [ + "audits[frame-title].description" + ], + "core/audits/accessibility/heading-order.js | title": [ + "audits[heading-order].title" + ], + "core/audits/accessibility/heading-order.js | description": [ + "audits[heading-order].description" + ], + "core/audits/accessibility/html-has-lang.js | title": [ + "audits[html-has-lang].title" + ], + "core/audits/accessibility/html-has-lang.js | description": [ + "audits[html-has-lang].description" + ], + "core/audits/accessibility/html-lang-valid.js | title": [ + "audits[html-lang-valid].title" + ], + "core/audits/accessibility/html-lang-valid.js | description": [ + "audits[html-lang-valid].description" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | title": [ + "audits[html-xml-lang-mismatch].title" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | description": [ + "audits[html-xml-lang-mismatch].description" + ], + "core/audits/accessibility/identical-links-same-purpose.js | title": [ + "audits[identical-links-same-purpose].title" + ], + "core/audits/accessibility/identical-links-same-purpose.js | description": [ + "audits[identical-links-same-purpose].description" + ], + "core/audits/accessibility/image-alt.js | title": [ + "audits[image-alt].title" + ], + "core/audits/accessibility/image-alt.js | description": [ + "audits[image-alt].description" + ], + "core/audits/accessibility/image-redundant-alt.js | title": [ + "audits[image-redundant-alt].title" + ], + "core/audits/accessibility/image-redundant-alt.js | description": [ + "audits[image-redundant-alt].description" + ], + "core/audits/accessibility/input-button-name.js | title": [ + "audits[input-button-name].title" + ], + "core/audits/accessibility/input-button-name.js | description": [ + "audits[input-button-name].description" + ], + "core/audits/accessibility/input-image-alt.js | title": [ + "audits[input-image-alt].title" + ], + "core/audits/accessibility/input-image-alt.js | description": [ + "audits[input-image-alt].description" + ], + "core/audits/accessibility/label-content-name-mismatch.js | title": [ + "audits[label-content-name-mismatch].title" + ], + "core/audits/accessibility/label-content-name-mismatch.js | description": [ + "audits[label-content-name-mismatch].description" + ], + "core/audits/accessibility/label.js | title": ["audits.label.title"], + "core/audits/accessibility/label.js | description": [ + "audits.label.description" + ], + "core/audits/accessibility/landmark-one-main.js | title": [ + "audits[landmark-one-main].title" + ], + "core/audits/accessibility/landmark-one-main.js | description": [ + "audits[landmark-one-main].description" + ], + "core/audits/accessibility/link-name.js | title": [ + "audits[link-name].title" + ], + "core/audits/accessibility/link-name.js | description": [ + "audits[link-name].description" + ], + "core/audits/accessibility/link-in-text-block.js | title": [ + "audits[link-in-text-block].title" + ], + "core/audits/accessibility/link-in-text-block.js | description": [ + "audits[link-in-text-block].description" + ], + "core/audits/accessibility/list.js | title": ["audits.list.title"], + "core/audits/accessibility/list.js | description": [ + "audits.list.description" + ], + "core/audits/accessibility/listitem.js | title": [ + "audits.listitem.title" + ], + "core/audits/accessibility/listitem.js | description": [ + "audits.listitem.description" + ], + "core/audits/accessibility/meta-refresh.js | title": [ + "audits[meta-refresh].title" + ], + "core/audits/accessibility/meta-refresh.js | description": [ + "audits[meta-refresh].description" + ], + "core/audits/accessibility/meta-viewport.js | title": [ + "audits[meta-viewport].title" + ], + "core/audits/accessibility/meta-viewport.js | description": [ + "audits[meta-viewport].description" + ], + "core/audits/accessibility/object-alt.js | title": [ + "audits[object-alt].title" + ], + "core/audits/accessibility/object-alt.js | description": [ + "audits[object-alt].description" + ], + "core/audits/accessibility/select-name.js | title": [ + "audits[select-name].title" + ], + "core/audits/accessibility/select-name.js | description": [ + "audits[select-name].description" + ], + "core/audits/accessibility/skip-link.js | title": [ + "audits[skip-link].title" + ], + "core/audits/accessibility/skip-link.js | description": [ + "audits[skip-link].description" + ], + "core/audits/accessibility/tabindex.js | title": [ + "audits.tabindex.title" + ], + "core/audits/accessibility/tabindex.js | description": [ + "audits.tabindex.description" + ], + "core/audits/accessibility/table-duplicate-name.js | title": [ + "audits[table-duplicate-name].title" + ], + "core/audits/accessibility/table-duplicate-name.js | description": [ + "audits[table-duplicate-name].description" + ], + "core/audits/accessibility/table-fake-caption.js | title": [ + "audits[table-fake-caption].title" + ], + "core/audits/accessibility/table-fake-caption.js | description": [ + "audits[table-fake-caption].description" + ], + "core/audits/accessibility/target-size.js | title": [ + "audits[target-size].title" + ], + "core/audits/accessibility/target-size.js | description": [ + "audits[target-size].description" + ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], + "core/audits/accessibility/td-headers-attr.js | title": [ + "audits[td-headers-attr].title" + ], + "core/audits/accessibility/td-headers-attr.js | description": [ + "audits[td-headers-attr].description" + ], + "core/audits/accessibility/th-has-data-cells.js | title": [ + "audits[th-has-data-cells].title" + ], + "core/audits/accessibility/th-has-data-cells.js | description": [ + "audits[th-has-data-cells].description" + ], + "core/audits/accessibility/valid-lang.js | title": [ + "audits[valid-lang].title" + ], + "core/audits/accessibility/valid-lang.js | description": [ + "audits[valid-lang].description" + ], + "core/audits/accessibility/video-caption.js | title": [ + "audits[video-caption].title" + ], + "core/audits/accessibility/video-caption.js | description": [ + "audits[video-caption].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | title": [ + "audits[uses-long-cache-ttl].title" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | description": [ + "audits[uses-long-cache-ttl].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | displayValue": [ + { + "values": { + "itemCount": 0 + }, + "path": "audits[uses-long-cache-ttl].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnCacheTTL": [ + "audits[uses-long-cache-ttl].details.headings[1].label", + "audits[cache-insight].details.headings[1].label" + ], + "core/audits/byte-efficiency/total-byte-weight.js | failureTitle": [ + "audits[total-byte-weight].title" + ], + "core/audits/byte-efficiency/total-byte-weight.js | description": [ + "audits[total-byte-weight].description" + ], + "core/audits/byte-efficiency/total-byte-weight.js | displayValue": [ + { + "values": { + "totalBytes": 2930513 + }, + "path": "audits[total-byte-weight].displayValue" + } + ], + "core/audits/byte-efficiency/offscreen-images.js | title": [ + "audits[offscreen-images].title" + ], + "core/audits/byte-efficiency/offscreen-images.js | description": [ + "audits[offscreen-images].description" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | title": [ + "audits[render-blocking-resources].title" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | description": [ + "audits[render-blocking-resources].description" + ], + "core/audits/byte-efficiency/unminified-css.js | title": [ + "audits[unminified-css].title" + ], + "core/audits/byte-efficiency/unminified-css.js | description": [ + "audits[unminified-css].description" + ], + "core/audits/byte-efficiency/unminified-javascript.js | title": [ + "audits[unminified-javascript].title" + ], + "core/audits/byte-efficiency/unminified-javascript.js | description": [ + "audits[unminified-javascript].description" + ], + "core/lib/i18n/i18n.js | displayValueByteSavings": [ + { + "values": { + "wastedBytes": 1000494 + }, + "path": "audits[unminified-javascript].displayValue" + }, + { + "values": { + "wastedBytes": 795305 + }, + "path": "audits[unused-javascript].displayValue" + }, + { + "values": { + "wastedBytes": 1892425 + }, + "path": "audits[uses-text-compression].displayValue" + }, + { + "values": { + "wastedBytes": 1561 + }, + "path": "audits[document-latency-insight].displayValue" + } + ], + "core/audits/byte-efficiency/unused-css-rules.js | title": [ + "audits[unused-css-rules].title" + ], + "core/audits/byte-efficiency/unused-css-rules.js | description": [ + "audits[unused-css-rules].description" + ], + "core/audits/byte-efficiency/unused-javascript.js | title": [ + "audits[unused-javascript].title" + ], + "core/audits/byte-efficiency/unused-javascript.js | description": [ + "audits[unused-javascript].description" + ], + "core/audits/byte-efficiency/modern-image-formats.js | title": [ + "audits[modern-image-formats].title" + ], + "core/audits/byte-efficiency/modern-image-formats.js | description": [ + "audits[modern-image-formats].description" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | title": [ + "audits[uses-optimized-images].title" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | description": [ + "audits[uses-optimized-images].description" + ], + "core/audits/byte-efficiency/uses-text-compression.js | title": [ + "audits[uses-text-compression].title" + ], + "core/audits/byte-efficiency/uses-text-compression.js | description": [ + "audits[uses-text-compression].description" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | title": [ + "audits[uses-responsive-images].title" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | description": [ + "audits[uses-responsive-images].description" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | title": [ + "audits[efficient-animated-content].title" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | description": [ + "audits[efficient-animated-content].description" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | title": [ + "audits[duplicated-javascript].title" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | description": [ + "audits[duplicated-javascript].description" + ], + "core/audits/byte-efficiency/legacy-javascript.js | title": [ + "audits[legacy-javascript].title" + ], + "core/audits/byte-efficiency/legacy-javascript.js | description": [ + "audits[legacy-javascript].description" + ], + "core/audits/dobetterweb/doctype.js | title": ["audits.doctype.title"], + "core/audits/dobetterweb/doctype.js | description": [ + "audits.doctype.description" + ], + "core/audits/dobetterweb/charset.js | title": ["audits.charset.title"], + "core/audits/dobetterweb/charset.js | description": [ + "audits.charset.description" + ], + "core/audits/dobetterweb/dom-size.js | title": ["audits[dom-size].title"], + "core/audits/dobetterweb/dom-size.js | description": [ + "audits[dom-size].description" + ], + "core/audits/dobetterweb/dom-size.js | displayValue": [ + { + "values": { + "itemCount": 101 + }, + "path": "audits[dom-size].displayValue" + } + ], + "core/audits/dobetterweb/dom-size.js | columnStatistic": [ + "audits[dom-size].details.headings[0].label" + ], + "core/audits/dobetterweb/dom-size.js | columnValue": [ + "audits[dom-size].details.headings[2].label" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMElements": [ + "audits[dom-size].details.items[0].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMDepth": [ + "audits[dom-size].details.items[1].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMWidth": [ + "audits[dom-size].details.items[2].statistic" + ], + "core/audits/dobetterweb/geolocation-on-start.js | title": [ + "audits[geolocation-on-start].title" + ], + "core/audits/dobetterweb/geolocation-on-start.js | description": [ + "audits[geolocation-on-start].description" + ], + "core/audits/dobetterweb/inspector-issues.js | title": [ + "audits[inspector-issues].title" + ], + "core/audits/dobetterweb/inspector-issues.js | description": [ + "audits[inspector-issues].description" + ], + "core/audits/dobetterweb/inspector-issues.js | columnIssueType": [ + "audits[inspector-issues].details.headings[0].label" + ], + "core/audits/dobetterweb/no-document-write.js | title": [ + "audits[no-document-write].title" + ], + "core/audits/dobetterweb/no-document-write.js | description": [ + "audits[no-document-write].description" + ], + "core/audits/dobetterweb/js-libraries.js | title": [ + "audits[js-libraries].title" + ], + "core/audits/dobetterweb/js-libraries.js | description": [ + "audits[js-libraries].description" + ], + "core/audits/dobetterweb/notification-on-start.js | title": [ + "audits[notification-on-start].title" + ], + "core/audits/dobetterweb/notification-on-start.js | description": [ + "audits[notification-on-start].description" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | title": [ + "audits[paste-preventing-inputs].title" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | description": [ + "audits[paste-preventing-inputs].description" + ], + "core/audits/dobetterweb/uses-http2.js | title": [ + "audits[uses-http2].title" + ], + "core/audits/dobetterweb/uses-http2.js | description": [ + "audits[uses-http2].description" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | title": [ + "audits[uses-passive-event-listeners].title" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | description": [ + "audits[uses-passive-event-listeners].description" + ], + "core/audits/seo/meta-description.js | title": [ + "audits[meta-description].title" + ], + "core/audits/seo/meta-description.js | description": [ + "audits[meta-description].description" + ], + "core/audits/seo/http-status-code.js | title": [ + "audits[http-status-code].title" + ], + "core/audits/seo/http-status-code.js | description": [ + "audits[http-status-code].description" + ], + "core/audits/seo/font-size.js | title": ["audits[font-size].title"], + "core/audits/seo/font-size.js | description": [ + "audits[font-size].description" + ], + "core/audits/seo/link-text.js | title": ["audits[link-text].title"], + "core/audits/seo/link-text.js | description": [ + "audits[link-text].description" + ], + "core/audits/seo/crawlable-anchors.js | title": [ + "audits[crawlable-anchors].title" + ], + "core/audits/seo/crawlable-anchors.js | description": [ + "audits[crawlable-anchors].description" + ], + "core/audits/seo/crawlable-anchors.js | columnFailingLink": [ + "audits[crawlable-anchors].details.headings[0].label" + ], + "core/audits/seo/is-crawlable.js | title": ["audits[is-crawlable].title"], + "core/audits/seo/is-crawlable.js | description": [ + "audits[is-crawlable].description" + ], + "core/audits/seo/robots-txt.js | failureTitle": [ + "audits[robots-txt].title" + ], + "core/audits/seo/robots-txt.js | description": [ + "audits[robots-txt].description" + ], + "core/audits/seo/robots-txt.js | displayValueValidationError": [ + { + "values": { + "itemCount": 52 + }, + "path": "audits[robots-txt].displayValue" + } + ], + "core/audits/seo/hreflang.js | title": ["audits.hreflang.title"], + "core/audits/seo/hreflang.js | description": [ + "audits.hreflang.description" + ], + "core/audits/seo/canonical.js | title": ["audits.canonical.title"], + "core/audits/seo/canonical.js | description": [ + "audits.canonical.description" + ], + "core/audits/seo/manual/structured-data.js | title": [ + "audits[structured-data].title" + ], + "core/audits/seo/manual/structured-data.js | description": [ + "audits[structured-data].description" + ], + "core/audits/bf-cache.js | failureTitle": ["audits[bf-cache].title"], + "core/audits/bf-cache.js | description": ["audits[bf-cache].description"], + "core/audits/bf-cache.js | displayValue": [ + { + "values": { + "itemCount": 1 + }, + "path": "audits[bf-cache].displayValue" + } + ], + "core/audits/bf-cache.js | failureReasonColumn": [ + "audits[bf-cache].details.headings[0].label" + ], + "core/audits/bf-cache.js | failureTypeColumn": [ + "audits[bf-cache].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/panels/application/components/BackForwardCacheStrings.js | webSocket": [ + "audits[bf-cache].details.items[0].reason" + ], + "core/audits/bf-cache.js | supportPendingFailureType": [ + "audits[bf-cache].details.items[0].failureType" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | title": [ + "audits[cache-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | description": [ + "audits[cache-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | requestColumn": [ + "audits[cache-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "core/audits/insights/cls-culprits-insight.js | columnScore": [ + "audits[cls-culprits-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | unsizedImage": [ + "audits[cls-culprits-insight].details.items[0].items[2].subItems.items[0].cause" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": [ + "audits[document-latency-insight].details.items.noRedirects.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": [ + { + "values": { + "PH1": "9 ms" + }, + "path": "audits[document-latency-insight].details.items.serverResponseIsFast.label" + } + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedTextCompression": [ + "audits[document-latency-insight].details.items.usesCompression.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | title": [ + "audits[duplicated-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | description": [ + "audits[duplicated-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnSource": [ + "audits[duplicated-javascript-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnDuplicatedBytes": [ + "audits[duplicated-javascript-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | totalReflowTime": [ + "audits[forced-reflow-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "core/lib/i18n/i18n.js | columnResourceSize": [ + "audits[image-delivery-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | title": [ + "audits[inp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | description": [ + "audits[inp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | title": [ + "audits[lcp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | description": [ + "audits[lcp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | subpart": [ + "audits[lcp-breakdown-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | timeToFirstByte": [ + "audits[lcp-breakdown-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | elementRenderDelay": [ + "audits[lcp-breakdown-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | title": [ + "audits[legacy-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | description": [ + "audits[legacy-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | columnWastedBytes": [ + "audits[legacy-javascript-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | title": [ + "audits[modern-http-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | description": [ + "audits[modern-http-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | protocol": [ + "audits[modern-http-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | title": [ + "audits[network-dependency-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | description": [ + "audits[network-dependency-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableTitle": [ + "audits[network-dependency-tree-insight].details.items[1].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableDescription": [ + "audits[network-dependency-tree-insight].details.items[1].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectOrigins": [ + "audits[network-dependency-tree-insight].details.items[1].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableTitle": [ + "audits[network-dependency-tree-insight].details.items[2].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableDescription": [ + "audits[network-dependency-tree-insight].details.items[2].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectCandidates": [ + "audits[network-dependency-tree-insight].details.items[2].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": [ + "audits[third-parties-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": [ + "audits[third-parties-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnMainThreadTime": [ + "audits[third-parties-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], + "core/config/default-config.js | performanceCategoryTitle": [ + "categories.performance.title" + ], + "core/config/default-config.js | a11yCategoryTitle": [ + "categories.accessibility.title" + ], + "core/config/default-config.js | a11yCategoryDescription": [ + "categories.accessibility.description" + ], + "core/config/default-config.js | a11yCategoryManualDescription": [ + "categories.accessibility.manualDescription" + ], + "core/config/default-config.js | bestPracticesCategoryTitle": [ + "categories[best-practices].title" + ], + "core/config/default-config.js | seoCategoryTitle": [ + "categories.seo.title" + ], + "core/config/default-config.js | seoCategoryDescription": [ + "categories.seo.description" + ], + "core/config/default-config.js | seoCategoryManualDescription": [ + "categories.seo.manualDescription" + ], + "core/config/default-config.js | metricGroupTitle": [ + "categoryGroups.metrics.title" + ], + "core/config/default-config.js | insightsGroupTitle": [ + "categoryGroups.insights.title" + ], + "core/config/default-config.js | insightsGroupDescription": [ + "categoryGroups.insights.description" + ], + "core/config/default-config.js | diagnosticsGroupTitle": [ + "categoryGroups.diagnostics.title" + ], + "core/config/default-config.js | diagnosticsGroupDescription": [ + "categoryGroups.diagnostics.description" + ], + "core/config/default-config.js | a11yBestPracticesGroupTitle": [ + "categoryGroups[a11y-best-practices].title" + ], + "core/config/default-config.js | a11yBestPracticesGroupDescription": [ + "categoryGroups[a11y-best-practices].description" + ], + "core/config/default-config.js | a11yColorContrastGroupTitle": [ + "categoryGroups[a11y-color-contrast].title" + ], + "core/config/default-config.js | a11yColorContrastGroupDescription": [ + "categoryGroups[a11y-color-contrast].description" + ], + "core/config/default-config.js | a11yNamesLabelsGroupTitle": [ + "categoryGroups[a11y-names-labels].title" + ], + "core/config/default-config.js | a11yNamesLabelsGroupDescription": [ + "categoryGroups[a11y-names-labels].description" + ], + "core/config/default-config.js | a11yNavigationGroupTitle": [ + "categoryGroups[a11y-navigation].title" + ], + "core/config/default-config.js | a11yNavigationGroupDescription": [ + "categoryGroups[a11y-navigation].description" + ], + "core/config/default-config.js | a11yAriaGroupTitle": [ + "categoryGroups[a11y-aria].title" + ], + "core/config/default-config.js | a11yAriaGroupDescription": [ + "categoryGroups[a11y-aria].description" + ], + "core/config/default-config.js | a11yLanguageGroupTitle": [ + "categoryGroups[a11y-language].title" + ], + "core/config/default-config.js | a11yLanguageGroupDescription": [ + "categoryGroups[a11y-language].description" + ], + "core/config/default-config.js | a11yAudioVideoGroupTitle": [ + "categoryGroups[a11y-audio-video].title" + ], + "core/config/default-config.js | a11yAudioVideoGroupDescription": [ + "categoryGroups[a11y-audio-video].description" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupTitle": [ + "categoryGroups[a11y-tables-lists].title" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupDescription": [ + "categoryGroups[a11y-tables-lists].description" + ], + "core/config/default-config.js | seoMobileGroupTitle": [ + "categoryGroups[seo-mobile].title" + ], + "core/config/default-config.js | seoMobileGroupDescription": [ + "categoryGroups[seo-mobile].description" + ], + "core/config/default-config.js | seoContentGroupTitle": [ + "categoryGroups[seo-content].title" + ], + "core/config/default-config.js | seoContentGroupDescription": [ + "categoryGroups[seo-content].description" + ], + "core/config/default-config.js | seoCrawlingGroupTitle": [ + "categoryGroups[seo-crawl].title" + ], + "core/config/default-config.js | seoCrawlingGroupDescription": [ + "categoryGroups[seo-crawl].description" + ], + "core/config/default-config.js | bestPracticesTrustSafetyGroupTitle": [ + "categoryGroups[best-practices-trust-safety].title" + ], + "core/config/default-config.js | bestPracticesUXGroupTitle": [ + "categoryGroups[best-practices-ux].title" + ], + "core/config/default-config.js | bestPracticesBrowserCompatGroupTitle": [ + "categoryGroups[best-practices-browser-compat].title" + ], + "core/config/default-config.js | bestPracticesGeneralGroupTitle": [ + "categoryGroups[best-practices-general].title" + ] + } + } +} diff --git a/docs/performance/lighthouse-baseline-pre-busca-mobile.json b/docs/performance/lighthouse-baseline-pre-busca-mobile.json new file mode 100644 index 00000000..c216c419 --- /dev/null +++ b/docs/performance/lighthouse-baseline-pre-busca-mobile.json @@ -0,0 +1,17427 @@ +{ + "lighthouseVersion": "12.8.2", + "requestedUrl": "http://localhost:5173/", + "mainDocumentUrl": "http://localhost:5173/", + "finalDisplayedUrl": "http://localhost:5173/", + "finalUrl": "http://localhost:5173/", + "fetchTime": "2026-06-04T03:32:48.831Z", + "gatherMode": "navigation", + "runWarnings": [], + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "environment": { + "networkUserAgent": "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36", + "hostUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "benchmarkIndex": 1209.5, + "credits": { + "axe-core": "4.12.0" + } + }, + "audits": { + "is-on-https": { + "id": "is-on-https", + "title": "Uses HTTPS", + "description": "All sites should be protected with HTTPS, even ones that don't handle sensitive data. This includes avoiding [mixed content](https://developers.google.com/web/fundamentals/security/prevent-mixed-content/what-is-mixed-content), where some resources are loaded over HTTP despite the initial request being served over HTTPS. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more about HTTPS](https://developer.chrome.com/docs/lighthouse/pwa/is-on-https/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Insecure URL" + }, + { + "key": "resolution", + "valueType": "text", + "label": "Request Resolution" + } + ], + "items": [] + } + }, + "redirects-http": { + "id": "redirects-http", + "title": "Redirects HTTP traffic to HTTPS", + "description": "Make sure that you redirect all HTTP traffic to HTTPS in order to enable secure web features for all your users. [Learn more](https://developer.chrome.com/docs/lighthouse/pwa/redirects-http/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "viewport": { + "id": "viewport", + "title": "Has a `` tag with `width` or `initial-scale`", + "description": "A `` not only optimizes your app for mobile screen sizes, but also prevents [a 300 millisecond delay to user input](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/). [Learn more about using the viewport meta tag](https://developer.chrome.com/docs/lighthouse/pwa/viewport/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "debugdata", + "viewportContent": "width=device-width, initial-scale=1.0" + }, + "guidanceLevel": 3 + }, + "first-contentful-paint": { + "id": "first-contentful-paint", + "title": "First Contentful Paint", + "description": "First Contentful Paint marks the time at which the first text or image is painted. [Learn more about the First Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint/).", + "score": 0, + "scoreDisplayMode": "numeric", + "numericValue": 10536.3793, + "numericUnit": "millisecond", + "displayValue": "10.5 s", + "scoringOptions": { + "p10": 1800, + "median": 3000 + } + }, + "largest-contentful-paint": { + "id": "largest-contentful-paint", + "title": "Largest Contentful Paint", + "description": "Largest Contentful Paint marks the time at which the largest text or image is painted. [Learn more about the Largest Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 0, + "scoreDisplayMode": "numeric", + "numericValue": 18092.172400000014, + "numericUnit": "millisecond", + "displayValue": "18.1 s", + "scoringOptions": { + "p10": 2500, + "median": 4000 + } + }, + "first-meaningful-paint": { + "id": "first-meaningful-paint", + "title": "First Meaningful Paint", + "description": "First Meaningful Paint measures when the primary content of a page is visible. [Learn more about the First Meaningful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "speed-index": { + "id": "speed-index", + "title": "Speed Index", + "description": "Speed Index shows how quickly the contents of a page are visibly populated. [Learn more about the Speed Index metric](https://developer.chrome.com/docs/lighthouse/performance/speed-index/).", + "score": 0.07, + "scoreDisplayMode": "numeric", + "numericValue": 10536.3793, + "numericUnit": "millisecond", + "displayValue": "10.5 s", + "scoringOptions": { + "p10": 3387, + "median": 5800 + } + }, + "screenshot-thumbnails": { + "id": "screenshot-thumbnails", + "title": "Screenshot Thumbnails", + "description": "This is what the load of your site looked like.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "filmstrip", + "scale": 3000, + "items": [ + { + "timing": 375, + "timestamp": 27098800347, + "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACMAPoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//9k=" + }, + { + "timing": 750, + "timestamp": 27099175347, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1125, + "timestamp": 27099550347, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1500, + "timestamp": 27099925347, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1875, + "timestamp": 27100300347, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2250, + "timestamp": 27100675347, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2625, + "timestamp": 27101050347, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 3000, + "timestamp": 27101425347, + "data": "data:image/jpeg;base64,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" + } + ] + } + }, + "final-screenshot": { + "id": "final-screenshot", + "title": "Final Screenshot", + "description": "The last screenshot captured of the pageload.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "screenshot", + "timing": 1334, + "timestamp": 27099759082, + "data": "data:image/jpeg;base64,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" + } + }, + "total-blocking-time": { + "id": "total-blocking-time", + "title": "Total Blocking Time", + "description": "Sum of all time periods between FCP and Time to Interactive, when task length exceeded 50ms, expressed in milliseconds. [Learn more about the Total Blocking Time metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/).", + "score": 0.85, + "scoreDisplayMode": "numeric", + "numericValue": 237.85345000000234, + "numericUnit": "millisecond", + "displayValue": "240 ms", + "scoringOptions": { + "p10": 200, + "median": 600 + } + }, + "max-potential-fid": { + "id": "max-potential-fid", + "title": "Max Potential First Input Delay", + "description": "The maximum potential First Input Delay that your users could experience is the duration of the longest task. [Learn more about the Maximum Potential First Input Delay metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-max-potential-fid/).", + "score": 0.86, + "scoreDisplayMode": "numeric", + "numericValue": 142, + "numericUnit": "millisecond", + "displayValue": "140 ms" + }, + "cumulative-layout-shift": { + "id": "cumulative-layout-shift", + "title": "Cumulative Layout Shift", + "description": "Cumulative Layout Shift measures the movement of visible elements within the viewport. [Learn more about the Cumulative Layout Shift metric](https://web.dev/articles/cls).", + "score": 0.68, + "scoreDisplayMode": "numeric", + "numericValue": 0.176479, + "numericUnit": "unitless", + "displayValue": "0.176", + "scoringOptions": { + "p10": 0.1, + "median": 0.25 + }, + "details": { + "type": "debugdata", + "items": [ + { + "cumulativeLayoutShiftMainFrame": 0.176479, + "newEngineResult": { + "cumulativeLayoutShift": 0.176479, + "cumulativeLayoutShiftMainFrame": 0.176479 + }, + "newEngineResultDiffered": false + } + ] + } + }, + "errors-in-console": { + "id": "errors-in-console", + "title": "Browser errors were logged to the console", + "description": "Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns. [Learn more about this errors in console diagnostic audit](https://developer.chrome.com/docs/lighthouse/best-practices/errors-in-console/)", + "score": 0, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "sourceLocation", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "description", + "valueType": "code", + "label": "Description" + } + ], + "items": [ + { + "source": "network", + "description": "Failed to load resource: net::ERR_ADDRESS_UNREACHABLE", + "sourceLocation": { + "type": "source-location", + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/auth/me/", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/auth/me/", + "urlProvider": "network", + "line": 0, + "column": 0 + } + } + ] + } + }, + "server-response-time": { + "id": "server-response-time", + "title": "Initial server response time was short", + "description": "Keep the server response time for the main document short because all other requests depend on it. [Learn more about the Time to First Byte metric](https://developer.chrome.com/docs/lighthouse/performance/time-to-first-byte/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 5.055999999999999, + "numericUnit": "millisecond", + "displayValue": "Root document took 10 ms", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "responseTime", + "valueType": "timespanMs", + "label": "Time Spent" + } + ], + "items": [ + { + "url": "http://localhost:5173/", + "responseTime": 5.055999999999999 + } + ], + "overallSavingsMs": 0 + }, + "guidanceLevel": 1 + }, + "interactive": { + "id": "interactive", + "title": "Time to Interactive", + "description": "Time to Interactive is the amount of time it takes for the page to become fully interactive. [Learn more about the Time to Interactive metric](https://developer.chrome.com/docs/lighthouse/performance/interactive/).", + "score": 0.03, + "scoreDisplayMode": "numeric", + "numericValue": 18302.87930000001, + "numericUnit": "millisecond", + "displayValue": "18.3 s" + }, + "user-timings": { + "id": "user-timings", + "title": "User Timing marks and measures", + "description": "Consider instrumenting your app with the User Timing API to measure your app's real-world performance during key user experiences. [Learn more about User Timing marks](https://developer.chrome.com/docs/lighthouse/performance/user-timings/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "16 user timings", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "timingType", + "valueType": "text", + "label": "Type" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 0.01, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 0.01, + "label": "Duration" + } + ], + "items": [ + { + "name": "Update Blocked", + "startTime": 397, + "duration": 12, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 411.5, + "duration": 53.3, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 468, + "duration": 0.399, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 470.5, + "duration": 1.199, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 634, + "duration": 13.099, + "timingType": "Measure" + }, + { + "name": "Update Blocked", + "startTime": 679.599, + "duration": 5.401, + "timingType": "Measure" + }, + { + "name": "​PageLayout", + "startTime": 689.3, + "duration": 16.5, + "timingType": "Measure" + }, + { + "name": "​Link", + "startTime": 692.199, + "duration": 0.4, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 692.599, + "duration": 0.6, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 693.199, + "duration": 0.4, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 693.599, + "duration": 0.401, + "timingType": "Measure" + }, + { + "name": "​NavLink", + "startTime": 694, + "duration": 0.399, + "timingType": "Measure" + }, + { + "name": "​Button", + "startTime": 694.399, + "duration": 0.2, + "timingType": "Measure" + }, + { + "name": "​Button", + "startTime": 694.599, + "duration": 0.1, + "timingType": "Measure" + }, + { + "name": "Mount", + "startTime": 695.5, + "duration": 0.099, + "timingType": "Measure" + }, + { + "name": "​Link", + "startTime": 704, + "duration": 0.699, + "timingType": "Measure" + } + ] + }, + "guidanceLevel": 2 + }, + "critical-request-chains": { + "id": "critical-request-chains", + "title": "Avoid chaining critical requests", + "description": "The Critical Request Chains below show you what resources are loaded with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load. [Learn how to avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "55 chains found", + "details": { + "type": "criticalrequestchain", + "chains": { + "285C12AFD6B4C9406E9A40BA30F26945": { + "request": { + "url": "http://localhost:5173/", + "startTime": 27098.431347, + "endTime": 27098.437792, + "responseReceivedTime": 27098.437092000004, + "transferSize": 2584 + }, + "children": { + "182882.8": { + "request": { + "url": "http://localhost:5173/@react-refresh", + "startTime": 27098.465443, + "endTime": 27098.495144, + "responseReceivedTime": 27098.493413, + "transferSize": 112187 + } + }, + "182882.9": { + "request": { + "url": "http://localhost:5173/site.webmanifest", + "startTime": 27098.466109, + "endTime": 27098.512208, + "responseReceivedTime": 27098.506723000002, + "transferSize": 796 + } + }, + "182882.2": { + "request": { + "url": "http://localhost:5173/@vite/client", + "startTime": 27098.463319, + "endTime": 27098.485128, + "responseReceivedTime": 27098.482833, + "transferSize": 210114 + }, + "children": { + "182882.16": { + "request": { + "url": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "startTime": 27098.492445, + "endTime": 27098.515073, + "responseReceivedTime": 27098.514345, + "transferSize": 3768 + } + } + } + }, + "182882.3": { + "request": { + "url": "http://localhost:5173/src/main.tsx", + "startTime": 27098.463873, + "endTime": 27098.48458, + "responseReceivedTime": 27098.483668, + "transferSize": 2330 + }, + "children": { + "182882.10": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "startTime": 27098.489313, + "endTime": 27098.511507, + "responseReceivedTime": 27098.505053999997, + "transferSize": 38613 + } + }, + "182882.11": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "startTime": 27098.489652, + "endTime": 27098.511022, + "responseReceivedTime": 27098.501193999997, + "transferSize": 821294 + }, + "children": { + "182882.41": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "startTime": 27098.558692, + "endTime": 27098.611876, + "responseReceivedTime": 27098.606658999997, + "transferSize": 14940 + } + } + } + }, + "182882.12": { + "request": { + "url": "http://localhost:5173/src/styles/global.css", + "startTime": 27098.48993, + "endTime": 27098.51176, + "responseReceivedTime": 27098.507671, + "transferSize": 16873 + } + }, + "182882.13": { + "request": { + "url": "http://localhost:5173/src/App.tsx", + "startTime": 27098.490469999997, + "endTime": 27098.510709, + "responseReceivedTime": 27098.508147999997, + "transferSize": 2529 + }, + "children": { + "182882.18": { + "request": { + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "startTime": 27098.523606, + "endTime": 27098.526172, + "responseReceivedTime": 27098.52506, + "transferSize": 21611 + }, + "children": { + "182882.21": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "startTime": 27098.550149, + "endTime": 27098.566977, + "responseReceivedTime": 27098.563519000003, + "transferSize": 399267 + } + }, + "182882.22": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "startTime": 27098.550417, + "endTime": 27098.566199, + "responseReceivedTime": 27098.564734, + "transferSize": 3458 + } + }, + "182882.23": { + "request": { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "startTime": 27098.55066, + "endTime": 27098.566618, + "responseReceivedTime": 27098.565586, + "transferSize": 9815 + }, + "children": { + "182882.42": { + "request": { + "url": "http://localhost:5173/src/components/ErrorFallback.css", + "startTime": 27098.572754, + "endTime": 27098.611385, + "responseReceivedTime": 27098.607572, + "transferSize": 3123 + } + } + } + }, + "182882.24": { + "request": { + "url": "http://localhost:5173/src/pages/Home.tsx", + "startTime": 27098.550893, + "endTime": 27098.568243, + "responseReceivedTime": 27098.567557, + "transferSize": 22299 + }, + "children": { + "182882.43": { + "request": { + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "startTime": 27098.584656, + "endTime": 27098.612221, + "responseReceivedTime": 27098.608748, + "transferSize": 4791 + }, + "children": { + "182882.78": { + "request": { + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "startTime": 27098.62079, + "endTime": 27098.674044, + "responseReceivedTime": 27098.672103999997, + "transferSize": 21741 + }, + "children": { + "182882.92": { + "request": { + "url": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "startTime": 27098.684951, + "endTime": 27098.700484, + "responseReceivedTime": 27098.6999, + "transferSize": 20091 + } + }, + "182882.93": { + "request": { + "url": "http://localhost:5173/src/components/layout/Navbar.css", + "startTime": 27098.6855, + "endTime": 27098.701386, + "responseReceivedTime": 27098.700802, + "transferSize": 8587 + } + } + } + }, + "182882.79": { + "request": { + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "startTime": 27098.620972, + "endTime": 27098.688014, + "responseReceivedTime": 27098.678820999998, + "transferSize": 21715 + }, + "children": { + "182882.94": { + "request": { + "url": "http://localhost:5173/src/components/layout/Footer.css", + "startTime": 27098.695173, + "endTime": 27098.705855, + "responseReceivedTime": 27098.701989, + "transferSize": 5121 + } + } + } + }, + "182882.80": { + "request": { + "url": "http://localhost:5173/src/components/layout/PageLayout.css", + "startTime": 27098.621363, + "endTime": 27098.683827, + "responseReceivedTime": 27098.679647, + "transferSize": 1789 + } + } + } + }, + "182882.44": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "startTime": 27098.584911, + "endTime": 27098.612423, + "responseReceivedTime": 27098.609299000003, + "transferSize": 12632 + }, + "children": { + "182882.76": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCard.css", + "startTime": 27098.620306, + "endTime": 27098.673366, + "responseReceivedTime": 27098.670863000003, + "transferSize": 6902 + } + } + } + }, + "182882.45": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "startTime": 27098.58519, + "endTime": 27098.61261, + "responseReceivedTime": 27098.610345, + "transferSize": 35878 + }, + "children": { + "182882.77": { + "request": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "startTime": 27098.620584, + "endTime": 27098.672873, + "responseReceivedTime": 27098.671540999996, + "transferSize": 3997 + } + } + } + }, + "182882.46": { + "request": { + "url": "http://localhost:5173/src/services/articleService.ts", + "startTime": 27098.58543, + "endTime": 27098.612827, + "responseReceivedTime": 27098.61086, + "transferSize": 9838 + } + }, + "182882.47": { + "request": { + "url": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "startTime": 27098.585652, + "endTime": 27098.622603, + "responseReceivedTime": 27098.618132000003, + "transferSize": 689 + } + }, + "182882.48": { + "request": { + "url": "http://localhost:5173/src/pages/Home.css", + "startTime": 27098.587644, + "endTime": 27098.623629, + "responseReceivedTime": 27098.618762999995, + "transferSize": 9483 + } + } + } + }, + "182882.25": { + "request": { + "url": "http://localhost:5173/src/pages/News.tsx", + "startTime": 27098.551155, + "endTime": 27098.569109, + "responseReceivedTime": 27098.568565999998, + "transferSize": 31303 + }, + "children": { + "182882.53": { + "request": { + "url": "http://localhost:5173/src/utils/categoryVariant.ts", + "startTime": 27098.589392, + "endTime": 27098.627152, + "responseReceivedTime": 27098.625587000002, + "transferSize": 2562 + } + }, + "182882.54": { + "request": { + "url": "http://localhost:5173/src/pages/News.css", + "startTime": 27098.589612, + "endTime": 27098.627413, + "responseReceivedTime": 27098.626514, + "transferSize": 1882 + } + } + } + }, + "182882.26": { + "request": { + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "startTime": 27098.551402, + "endTime": 27098.570351, + "responseReceivedTime": 27098.569424, + "transferSize": 19118 + }, + "children": { + "182882.49": { + "request": { + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "startTime": 27098.587908, + "endTime": 27098.622988, + "responseReceivedTime": 27098.619312, + "transferSize": 3750 + }, + "children": { + "182882.81": { + "request": { + "url": "http://localhost:5173/src/components/ui/Button.css", + "startTime": 27098.626224, + "endTime": 27098.687362, + "responseReceivedTime": 27098.682167, + "transferSize": 2160 + } + } + } + }, + "182882.50": { + "request": { + "url": "http://localhost:5173/src/services/newsletterService.ts", + "startTime": 27098.588418, + "endTime": 27098.623284, + "responseReceivedTime": 27098.619768999997, + "transferSize": 1363 + } + }, + "182882.51": { + "request": { + "url": "http://localhost:5173/src/utils/extractApiError.ts", + "startTime": 27098.588782, + "endTime": 27098.623817, + "responseReceivedTime": 27098.621584, + "transferSize": 7110 + } + }, + "182882.52": { + "request": { + "url": "http://localhost:5173/src/pages/Newsletter.css", + "startTime": 27098.589163, + "endTime": 27098.62442, + "responseReceivedTime": 27098.622127, + "transferSize": 4998 + } + } + } + }, + "182882.27": { + "request": { + "url": "http://localhost:5173/src/pages/About/index.tsx", + "startTime": 27098.551654999996, + "endTime": 27098.575059, + "responseReceivedTime": 27098.573051, + "transferSize": 5790 + }, + "children": { + "182882.56": { + "request": { + "url": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "startTime": 27098.59011, + "endTime": 27098.631644, + "responseReceivedTime": 27098.629349, + "transferSize": 15734 + } + }, + "182882.57": { + "request": { + "url": "http://localhost:5173/src/pages/About/About.css", + "startTime": 27098.590303, + "endTime": 27098.631442, + "responseReceivedTime": 27098.630676, + "transferSize": 3917 + } + } + } + }, + "182882.28": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "startTime": 27098.55195, + "endTime": 27098.57526, + "responseReceivedTime": 27098.574256, + "transferSize": 4765 + }, + "children": { + "182882.55": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "startTime": 27098.589872, + "endTime": 27098.628636, + "responseReceivedTime": 27098.627861, + "transferSize": 75711 + }, + "children": { + "182882.82": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/Legal.css", + "startTime": 27098.634875, + "endTime": 27098.688665, + "responseReceivedTime": 27098.682857, + "transferSize": 4112 + } + } + } + } + } + }, + "182882.29": { + "request": { + "url": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "startTime": 27098.552705, + "endTime": 27098.590973, + "responseReceivedTime": 27098.578242999996, + "transferSize": 4862 + } + }, + "182882.30": { + "request": { + "url": "http://localhost:5173/src/pages/Article.tsx", + "startTime": 27098.554147, + "endTime": 27098.591234, + "responseReceivedTime": 27098.580568, + "transferSize": 35202 + }, + "children": { + "182882.61": { + "request": { + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "startTime": 27098.60082, + "endTime": 27098.643832, + "responseReceivedTime": 27098.637899999998, + "transferSize": 4592 + }, + "children": { + "182882.87": { + "request": { + "url": "http://localhost:5173/src/components/ui/Avatar.css", + "startTime": 27098.650137, + "endTime": 27098.692825, + "responseReceivedTime": 27098.691322000002, + "transferSize": 1101 + } + } + } + }, + "182882.62": { + "request": { + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "startTime": 27098.601112, + "endTime": 27098.643329, + "responseReceivedTime": 27098.638489, + "transferSize": 3273 + }, + "children": { + "182882.86": { + "request": { + "url": "http://localhost:5173/src/components/ui/Badge.css", + "startTime": 27098.646092, + "endTime": 27098.692085, + "responseReceivedTime": 27098.690609999998, + "transferSize": 2103 + } + } + } + }, + "182882.63": { + "request": { + "url": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "startTime": 27098.60138, + "endTime": 27098.644101000005, + "responseReceivedTime": 27098.639043, + "transferSize": 15920 + } + }, + "182882.64": { + "request": { + "url": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "startTime": 27098.601597, + "endTime": 27098.644329, + "responseReceivedTime": 27098.64123, + "transferSize": 11522 + } + }, + "182882.65": { + "request": { + "url": "http://localhost:5173/src/utils/formatDate.ts", + "startTime": 27098.601897, + "endTime": 27098.644684, + "responseReceivedTime": 27098.642245000003, + "transferSize": 5113 + } + }, + "182882.66": { + "request": { + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "startTime": 27098.602222, + "endTime": 27098.649761, + "responseReceivedTime": 27098.649146000003, + "transferSize": 26599 + }, + "children": { + "182882.88": { + "request": { + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "startTime": 27098.652654, + "endTime": 27098.69738, + "responseReceivedTime": 27098.693504000003, + "transferSize": 24618 + }, + "children": { + "182882.97": { + "request": { + "url": "http://localhost:5173/src/components/ui/CommentItem.css", + "startTime": 27098.703104, + "endTime": 27098.70696, + "responseReceivedTime": 27098.706125999997, + "transferSize": 4263 + } + } + } + }, + "182882.89": { + "request": { + "url": "http://localhost:5173/src/services/commentService.ts", + "startTime": 27098.655766, + "endTime": 27098.697663, + "responseReceivedTime": 27098.69423, + "transferSize": 2883 + } + } + } + }, + "182882.67": { + "request": { + "url": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "startTime": 27098.602487, + "endTime": 27098.651757, + "responseReceivedTime": 27098.651085999998, + "transferSize": 6332 + } + }, + "182882.68": { + "request": { + "url": "http://localhost:5173/src/styles/article-body.css", + "startTime": 27098.602752, + "endTime": 27098.657583, + "responseReceivedTime": 27098.65204, + "transferSize": 2477 + } + }, + "182882.69": { + "request": { + "url": "http://localhost:5173/src/pages/Article.css", + "startTime": 27098.603099, + "endTime": 27098.658133, + "responseReceivedTime": 27098.652859, + "transferSize": 13288 + } + } + } + }, + "182882.31": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "startTime": 27098.554513, + "endTime": 27098.591438, + "responseReceivedTime": 27098.581121, + "transferSize": 13097 + }, + "children": { + "182882.58": { + "request": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "startTime": 27098.598448, + "endTime": 27098.63359, + "responseReceivedTime": 27098.632075999998, + "transferSize": 19518 + }, + "children": { + "182882.84": { + "request": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "startTime": 27098.640849, + "endTime": 27098.68891, + "responseReceivedTime": 27098.686721000002, + "transferSize": 5342 + }, + "children": { + "182882.95": { + "request": { + "url": "http://localhost:5173/src/assets/seek-white.svg?import", + "startTime": 27098.695557, + "endTime": 27098.706611, + "responseReceivedTime": 27098.70342, + "transferSize": 10333 + } + }, + "182882.96": { + "request": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "startTime": 27098.695858, + "endTime": 27098.705486, + "responseReceivedTime": 27098.704118999998, + "transferSize": 1532 + } + } + } + }, + "182882.85": { + "request": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.css", + "startTime": 27098.641071, + "endTime": 27098.693229, + "responseReceivedTime": 27098.690087, + "transferSize": 4726 + } + } + } + }, + "182882.59": { + "request": { + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "startTime": 27098.59885, + "endTime": 27098.634019, + "responseReceivedTime": 27098.632754, + "transferSize": 4439 + }, + "children": { + "182882.83": { + "request": { + "url": "http://localhost:5173/src/components/ui/Input.css", + "startTime": 27098.640597, + "endTime": 27098.688377, + "responseReceivedTime": 27098.685786000002, + "transferSize": 1768 + } + } + } + }, + "182882.60": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/Auth.css", + "startTime": 27098.599833, + "endTime": 27098.642714, + "responseReceivedTime": 27098.637172000002, + "transferSize": 3256 + } + } + } + }, + "182882.32": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "startTime": 27098.554807, + "endTime": 27098.591619, + "responseReceivedTime": 27098.585820999997, + "transferSize": 27959 + }, + "children": { + "182882.70": { + "request": { + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "startTime": 27098.603396, + "endTime": 27098.658412, + "responseReceivedTime": 27098.656021, + "transferSize": 13726 + }, + "children": { + "182882.91": { + "request": { + "url": "http://localhost:5173/src/components/ui/Modal.css", + "startTime": 27098.668328000003, + "endTime": 27098.699616, + "responseReceivedTime": 27098.699054, + "transferSize": 2244 + } + } + } + }, + "182882.71": { + "request": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "startTime": 27098.60365, + "endTime": 27098.658611, + "responseReceivedTime": 27098.656567, + "transferSize": 6890 + }, + "children": { + "182882.90": { + "request": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "startTime": 27098.667732, + "endTime": 27098.697905, + "responseReceivedTime": 27098.696335, + "transferSize": 1998 + } + } + } + }, + "182882.72": { + "request": { + "url": "http://localhost:5173/src/utils/passwordRules.ts", + "startTime": 27098.603969, + "endTime": 27098.657953, + "responseReceivedTime": 27098.657078, + "transferSize": 3410 + } + } + } + }, + "182882.33": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "startTime": 27098.55515, + "endTime": 27098.591787, + "responseReceivedTime": 27098.586463999996, + "transferSize": 12833 + } + }, + "182882.34": { + "request": { + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "startTime": 27098.555621, + "endTime": 27098.591967, + "responseReceivedTime": 27098.586956, + "transferSize": 18222 + } + }, + "182882.35": { + "request": { + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "startTime": 27098.555929, + "endTime": 27098.596034, + "responseReceivedTime": 27098.594073, + "transferSize": 55832 + }, + "children": { + "182882.73": { + "request": { + "url": "http://localhost:5173/src/pages/Perfil.css", + "startTime": 27098.604718, + "endTime": 27098.6605, + "responseReceivedTime": 27098.659851, + "transferSize": 4993 + } + } + } + }, + "182882.36": { + "request": { + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "startTime": 27098.556291, + "endTime": 27098.60432, + "responseReceivedTime": 27098.595139, + "transferSize": 10579 + }, + "children": { + "182882.74": { + "request": { + "url": "http://localhost:5173/src/pages/NotFound.css", + "startTime": 27098.607321, + "endTime": 27098.661911, + "responseReceivedTime": 27098.660782, + "transferSize": 2228 + } + } + } + }, + "182882.37": { + "request": { + "url": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "startTime": 27098.55663, + "endTime": 27098.605133, + "responseReceivedTime": 27098.596391999996, + "transferSize": 12734 + } + }, + "182882.38": { + "request": { + "url": "http://localhost:5173/src/router/AdminRoute.tsx", + "startTime": 27098.556956, + "endTime": 27098.605368, + "responseReceivedTime": 27098.597348000003, + "transferSize": 6218 + } + }, + "182882.39": { + "request": { + "url": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "startTime": 27098.557362, + "endTime": 27098.605484, + "responseReceivedTime": 27098.599105, + "transferSize": 10378 + } + } + } + } + } + }, + "182882.14": { + "request": { + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "startTime": 27098.490954, + "endTime": 27098.51195, + "responseReceivedTime": 27098.508559, + "transferSize": 11746 + }, + "children": { + "182882.19": { + "request": { + "url": "http://localhost:5173/src/services/authService.ts", + "startTime": 27098.523927, + "endTime": 27098.527571, + "responseReceivedTime": 27098.525720999998, + "transferSize": 5810 + }, + "children": { + "182882.40": { + "request": { + "url": "http://localhost:5173/src/services/api.ts", + "startTime": 27098.557923, + "endTime": 27098.605608, + "responseReceivedTime": 27098.600114, + "transferSize": 7075 + }, + "children": { + "182882.75": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "startTime": 27098.610106, + "endTime": 27098.67459, + "responseReceivedTime": 27098.668666999998, + "transferSize": 108020 + } + } + } + } + } + } + } + }, + "182882.15": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "startTime": 27098.491463, + "endTime": 27098.502812, + "responseReceivedTime": 27098.502081, + "transferSize": 10883 + }, + "children": { + "182882.17": { + "request": { + "url": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "startTime": 27098.506423, + "endTime": 27098.516454, + "responseReceivedTime": 27098.515899, + "transferSize": 7922 + } + } + } + } + } + } + } + } + }, + "longestChain": { + "duration": 275.61299999803305, + "length": 8, + "transferSize": 4263 + } + }, + "guidanceLevel": 1 + }, + "redirects": { + "id": "redirects", + "title": "Avoid multiple page redirects", + "description": "Redirects introduce additional delays before the page can be loaded. [Learn how to avoid page redirects](https://developer.chrome.com/docs/lighthouse/performance/redirects/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0 + }, + "guidanceLevel": 2 + }, + "image-aspect-ratio": { + "id": "image-aspect-ratio", + "title": "Displays images with correct aspect ratio", + "description": "Image display dimensions should match natural aspect ratio. [Learn more about image aspect ratio](https://developer.chrome.com/docs/lighthouse/best-practices/image-aspect-ratio/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Displayed)" + }, + { + "key": "actualAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Actual)" + } + ], + "items": [] + } + }, + "image-size-responsive": { + "id": "image-size-responsive", + "title": "Serves images with appropriate resolution", + "description": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn how to provide responsive images](https://web.dev/articles/serve-responsive-images).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedSize", + "valueType": "text", + "label": "Displayed size" + }, + { + "key": "actualSize", + "valueType": "text", + "label": "Actual size" + }, + { + "key": "expectedSize", + "valueType": "text", + "label": "Expected size" + } + ], + "items": [] + } + }, + "deprecations": { + "id": "deprecations", + "title": "Avoids deprecated APIs", + "description": "Deprecated APIs will eventually be removed from the browser. [Learn more about deprecated APIs](https://developer.chrome.com/docs/lighthouse/best-practices/deprecations/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "value", + "valueType": "text", + "label": "Deprecation / Warning" + }, + { + "key": "source", + "valueType": "source-location", + "label": "Source" + } + ], + "items": [] + } + }, + "third-party-cookies": { + "id": "third-party-cookies", + "title": "Avoids third-party cookies", + "description": "Third-party cookies may be blocked in some contexts. [Learn more about preparing for third-party cookie restrictions](https://privacysandbox.google.com/cookies/prepare/overview).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [] + } + }, + "mainthread-work-breakdown": { + "id": "mainthread-work-breakdown", + "title": "Minimize main-thread work", + "description": "Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to minimize main-thread work](https://developer.chrome.com/docs/lighthouse/performance/mainthread-work-breakdown/)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "numericValue": 2577.127999999985, + "numericUnit": "millisecond", + "displayValue": "2.6 s", + "metricSavings": { + "TBT": 250 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "groupLabel", + "valueType": "text", + "label": "Category" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "group": "other", + "groupLabel": "Other", + "duration": 1231.2519999999881 + }, + { + "group": "scriptEvaluation", + "groupLabel": "Script Evaluation", + "duration": 652.5839999999974 + }, + { + "group": "styleLayout", + "groupLabel": "Style & Layout", + "duration": 559.7280000000002 + }, + { + "group": "paintCompositeRender", + "groupLabel": "Rendering", + "duration": 74.64399999999999 + }, + { + "group": "scriptParseCompile", + "groupLabel": "Script Parsing & Compilation", + "duration": 37.74399999999999 + }, + { + "group": "garbageCollection", + "groupLabel": "Garbage Collection", + "duration": 16.24 + }, + { + "group": "parseHTML", + "groupLabel": "Parse HTML & CSS", + "duration": 4.935999999999999 + } + ], + "sortedBy": ["duration"] + }, + "guidanceLevel": 1 + }, + "bootup-time": { + "id": "bootup-time", + "title": "JavaScript execution time", + "description": "Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to reduce Javascript execution time](https://developer.chrome.com/docs/lighthouse/performance/bootup-time/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 640.5039999999997, + "numericUnit": "millisecond", + "displayValue": "0.6 s", + "metricSavings": { + "TBT": 250 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "total", + "granularity": 1, + "valueType": "ms", + "label": "Total CPU Time" + }, + { + "key": "scripting", + "granularity": 1, + "valueType": "ms", + "label": "Script Evaluation" + }, + { + "key": "scriptParseCompile", + "granularity": 1, + "valueType": "ms", + "label": "Script Parse" + } + ], + "items": [ + { + "url": "Unattributable", + "total": 928.6039999999871, + "scripting": 22.279999999999994, + "scriptParseCompile": 0 + }, + { + "url": "http://localhost:5173/", + "total": 640.92, + "scripting": 18.468, + "scriptParseCompile": 4.879999999999999 + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "total": 594.0879999999997, + "scripting": 494.12799999999964, + "scriptParseCompile": 9.716 + }, + { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "total": 93.96400000000004, + "scripting": 90.85200000000005, + "scriptParseCompile": 0.18 + } + ], + "summary": { + "wastedMs": 640.5039999999997 + }, + "sortedBy": ["total"] + }, + "guidanceLevel": 1 + }, + "uses-rel-preconnect": { + "id": "uses-rel-preconnect", + "title": "Preconnect to required origins", + "description": "Consider adding `preconnect` or `dns-prefetch` resource hints to establish early connections to important third-party origins. [Learn how to preconnect to required origins](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "warnings": [], + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0, + "sortedBy": ["wastedMs"] + }, + "guidanceLevel": 3 + }, + "font-display": { + "id": "font-display", + "title": "All text remains visible during webfont loads", + "description": "Leverage the `font-display` CSS feature to ensure text is user-visible while webfonts are loading. [Learn more about `font-display`](https://developer.chrome.com/docs/lighthouse/performance/font-display/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "diagnostics": { + "id": "diagnostics", + "title": "Diagnostics", + "description": "Collection of useful page vitals.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "debugdata", + "items": [ + { + "numRequests": 105, + "numScripts": 91, + "numStylesheets": 0, + "numFonts": 4, + "numTasks": 1749, + "numTasksOver10ms": 11, + "numTasksOver25ms": 3, + "numTasksOver50ms": 3, + "numTasksOver100ms": 1, + "numTasksOver500ms": 0, + "rtt": 0.0609, + "throughput": 188233484.26299593, + "maxRtt": 0.0609, + "maxServerLatency": 4.2930999999999955, + "totalByteWeight": 2930513, + "totalTaskTime": 644.2819999999988, + "mainDocumentTransferSize": 2584 + } + ] + } + }, + "network-requests": { + "id": "network-requests", + "title": "Network Requests", + "description": "Lists the network requests that were made during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + }, + { + "key": "networkRequestTime", + "valueType": "ms", + "granularity": 1, + "label": "Network Request Time" + }, + { + "key": "networkEndTime", + "valueType": "ms", + "granularity": 1, + "label": "Network End Time" + }, + { + "key": "transferSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Transfer Size" + }, + { + "key": "resourceSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Resource Size" + }, + { + "key": "statusCode", + "valueType": "text", + "label": "Status Code" + }, + { + "key": "mimeType", + "valueType": "text", + "label": "MIME Type" + }, + { + "key": "resourceType", + "valueType": "text", + "label": "Resource Type" + } + ], + "items": [ + { + "url": "http://localhost:5173/", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 0, + "networkRequestTime": 4.062000002712011, + "networkEndTime": 10.506999999284744, + "finished": true, + "transferSize": 2584, + "resourceSize": 2330, + "statusCode": 200, + "mimeType": "text/html", + "resourceType": "Document", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/@vite/client", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 26.537000000476837, + "networkRequestTime": 36.033999998122454, + "networkEndTime": 57.84299999848008, + "finished": true, + "transferSize": 210114, + "resourceSize": 209821, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/main.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 27.002000000327826, + "networkRequestTime": 36.58799999952316, + "networkEndTime": 57.29500000178814, + "finished": true, + "transferSize": 2330, + "resourceSize": 2040, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 27.199999999254942, + "networkRequestTime": 27.199999999254942, + "networkEndTime": 117.90599999949336, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "Script", + "priority": "Low", + "experimentalFromMainFrame": true, + "entity": "vlibras.gov.br" + }, + { + "url": "http://localhost:5173/@react-refresh", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 32.25, + "networkRequestTime": 38.157999999821186, + "networkEndTime": 67.85900000110269, + "finished": true, + "transferSize": 112187, + "resourceSize": 111894, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/site.webmanifest", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 36.10999999940395, + "networkRequestTime": 38.824000000953674, + "networkEndTime": 84.92300000041723, + "finished": true, + "transferSize": 796, + "resourceSize": 517, + "statusCode": 200, + "mimeType": "application/manifest+json", + "resourceType": "Manifest", + "priority": "Medium", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 61.489000000059605, + "networkRequestTime": 62.02799999713898, + "networkEndTime": 84.22199999913573, + "finished": true, + "transferSize": 38613, + "resourceSize": 38303, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 61.68800000101328, + "networkRequestTime": 62.3669999986887, + "networkEndTime": 83.73699999973178, + "finished": true, + "transferSize": 821294, + "resourceSize": 820982, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/styles/global.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 61.87799999862909, + "networkRequestTime": 62.644999999552965, + "networkEndTime": 84.47500000149012, + "finished": true, + "transferSize": 16873, + "resourceSize": 16581, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/App.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 62.05199999734759, + "networkRequestTime": 63.184999998658895, + "networkEndTime": 83.4239999987185, + "finished": true, + "transferSize": 2529, + "resourceSize": 2239, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 62.21299999952316, + "networkRequestTime": 63.66899999976158, + "networkEndTime": 84.66499999910593, + "finished": true, + "transferSize": 11746, + "resourceSize": 11454, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 62.360000003129244, + "networkRequestTime": 64.17799999937415, + "networkEndTime": 75.52699999883771, + "finished": true, + "transferSize": 10883, + "resourceSize": 10573, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 63.85200000181794, + "networkRequestTime": 65.16000000014901, + "networkEndTime": 87.7879999987781, + "finished": true, + "transferSize": 3768, + "resourceSize": 3478, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 77.85500000044703, + "networkRequestTime": 79.13800000026822, + "networkEndTime": 89.16899999976158, + "finished": true, + "transferSize": 7922, + "resourceSize": 7614, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 95.12999999895692, + "networkRequestTime": 96.32099999859929, + "networkEndTime": 98.88700000196695, + "finished": true, + "transferSize": 21611, + "resourceSize": 21319, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/authService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 95.54500000178814, + "networkRequestTime": 96.64199999719858, + "networkEndTime": 100.28599999845028, + "finished": true, + "transferSize": 5810, + "resourceSize": 5520, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 122.36099999770522, + "networkRequestTime": 122.8640000000596, + "networkEndTime": 139.69199999794364, + "finished": true, + "transferSize": 399267, + "resourceSize": 398955, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 122.60200000181794, + "networkRequestTime": 123.13199999928474, + "networkEndTime": 138.91400000080466, + "finished": true, + "transferSize": 3458, + "resourceSize": 3150, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 122.78999999910593, + "networkRequestTime": 123.375, + "networkEndTime": 139.33300000056624, + "finished": true, + "transferSize": 9815, + "resourceSize": 9524, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Home.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 122.91999999806285, + "networkRequestTime": 123.60799999907613, + "networkEndTime": 140.95800000056624, + "finished": true, + "transferSize": 22299, + "resourceSize": 22007, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/News.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.04800000041723, + "networkRequestTime": 123.87000000104308, + "networkEndTime": 141.82400000095367, + "finished": true, + "transferSize": 31303, + "resourceSize": 31011, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.1630000025034, + "networkRequestTime": 124.11700000241399, + "networkEndTime": 143.06599999964237, + "finished": true, + "transferSize": 19118, + "resourceSize": 18826, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/About/index.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.27600000053644, + "networkRequestTime": 124.36999999731779, + "networkEndTime": 147.77400000020862, + "finished": true, + "transferSize": 5790, + "resourceSize": 5500, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.40399999916553, + "networkRequestTime": 124.66499999910593, + "networkEndTime": 147.97500000149012, + "finished": true, + "transferSize": 4765, + "resourceSize": 4475, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.5109999999404, + "networkRequestTime": 125.41999999806285, + "networkEndTime": 163.687999997288, + "finished": true, + "transferSize": 4862, + "resourceSize": 4572, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Article.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.64499999955297, + "networkRequestTime": 126.86199999973178, + "networkEndTime": 163.94900000095367, + "finished": true, + "transferSize": 35202, + "resourceSize": 34910, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.83100000023842, + "networkRequestTime": 127.22800000011921, + "networkEndTime": 164.15300000086427, + "finished": true, + "transferSize": 13097, + "resourceSize": 12805, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 124.00499999895692, + "networkRequestTime": 127.52199999988079, + "networkEndTime": 164.3339999988675, + "finished": true, + "transferSize": 27959, + "resourceSize": 27667, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 124.21099999919534, + "networkRequestTime": 127.86499999836087, + "networkEndTime": 164.50200000032783, + "finished": true, + "transferSize": 12833, + "resourceSize": 12541, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 124.3369999974966, + "networkRequestTime": 128.33599999919534, + "networkEndTime": 164.6820000000298, + "finished": true, + "transferSize": 18222, + "resourceSize": 17930, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 124.5380000025034, + "networkRequestTime": 128.6439999975264, + "networkEndTime": 168.74899999797344, + "finished": true, + "transferSize": 55832, + "resourceSize": 55540, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 124.89000000059605, + "networkRequestTime": 129.00600000098348, + "networkEndTime": 177.035000000149, + "finished": true, + "transferSize": 10579, + "resourceSize": 10287, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 125.39800000190735, + "networkRequestTime": 129.3449999988079, + "networkEndTime": 177.8480000011623, + "finished": true, + "transferSize": 12734, + "resourceSize": 12442, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/router/AdminRoute.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 126.1820000000298, + "networkRequestTime": 129.6710000000894, + "networkEndTime": 178.08300000056624, + "finished": true, + "transferSize": 6218, + "resourceSize": 5928, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 126.48999999836087, + "networkRequestTime": 130.07699999958277, + "networkEndTime": 178.19900000095367, + "finished": true, + "transferSize": 10378, + "resourceSize": 10086, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/api.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 127.8260000012815, + "networkRequestTime": 130.63800000026822, + "networkEndTime": 178.3230000026524, + "finished": true, + "transferSize": 7075, + "resourceSize": 6785, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 128.21799999848008, + "networkRequestTime": 131.40699999779463, + "networkEndTime": 184.59099999815226, + "finished": true, + "transferSize": 14940, + "resourceSize": 14630, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ErrorFallback.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 143.035000000149, + "networkRequestTime": 145.46900000050664, + "networkEndTime": 184.10000000149012, + "finished": true, + "transferSize": 3123, + "resourceSize": 2833, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 147.54399999976158, + "networkRequestTime": 157.37099999934435, + "networkEndTime": 184.93600000068545, + "finished": true, + "transferSize": 4791, + "resourceSize": 4501, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 147.77499999850988, + "networkRequestTime": 157.62600000202656, + "networkEndTime": 185.13800000026822, + "finished": true, + "transferSize": 12632, + "resourceSize": 12340, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 147.964999999851, + "networkRequestTime": 157.9050000011921, + "networkEndTime": 185.32499999925494, + "finished": true, + "transferSize": 35878, + "resourceSize": 35586, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/articleService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 148.12400000169873, + "networkRequestTime": 158.14499999955297, + "networkEndTime": 185.54199999943376, + "finished": true, + "transferSize": 9838, + "resourceSize": 9548, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 148.28700000047684, + "networkRequestTime": 158.367000002414, + "networkEndTime": 195.3179999999702, + "finished": true, + "transferSize": 689, + "resourceSize": 401, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Home.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 148.5080000013113, + "networkRequestTime": 160.3589999973774, + "networkEndTime": 196.34400000050664, + "finished": true, + "transferSize": 9483, + "resourceSize": 9192, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 149.11899999901652, + "networkRequestTime": 160.62299999967217, + "networkEndTime": 195.70299999788404, + "finished": true, + "transferSize": 3750, + "resourceSize": 3460, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/newsletterService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 149.30299999937415, + "networkRequestTime": 161.132999997586, + "networkEndTime": 195.99900000169873, + "finished": true, + "transferSize": 1363, + "resourceSize": 1073, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/extractApiError.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 149.45999999716878, + "networkRequestTime": 161.49699999764562, + "networkEndTime": 196.53199999779463, + "finished": true, + "transferSize": 7110, + "resourceSize": 6820, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Newsletter.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 149.617000002414, + "networkRequestTime": 161.8779999986291, + "networkEndTime": 197.13500000163913, + "finished": true, + "transferSize": 4998, + "resourceSize": 4707, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/categoryVariant.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 150.33599999919534, + "networkRequestTime": 162.10700000077486, + "networkEndTime": 199.867000002414, + "finished": true, + "transferSize": 2562, + "resourceSize": 2272, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/News.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 150.59999999776483, + "networkRequestTime": 162.32699999958277, + "networkEndTime": 200.1279999986291, + "finished": true, + "transferSize": 1882, + "resourceSize": 1592, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 160.58299999684095, + "networkRequestTime": 162.5870000012219, + "networkEndTime": 201.35099999979138, + "finished": true, + "transferSize": 75711, + "resourceSize": 75419, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 160.95500000193715, + "networkRequestTime": 162.82499999925494, + "networkEndTime": 204.3590000011027, + "finished": true, + "transferSize": 15734, + "resourceSize": 15442, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/About/About.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 161.1849999986589, + "networkRequestTime": 163.01799999922514, + "networkEndTime": 204.15700000151992, + "finished": true, + "transferSize": 3917, + "resourceSize": 3627, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 170.33300000056624, + "networkRequestTime": 171.1629999987781, + "networkEndTime": 206.30499999970198, + "finished": true, + "transferSize": 19518, + "resourceSize": 19226, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 170.68100000172853, + "networkRequestTime": 171.5649999976158, + "networkEndTime": 206.7340000011027, + "finished": true, + "transferSize": 4439, + "resourceSize": 4149, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Auth/Auth.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 170.9050000011921, + "networkRequestTime": 172.54800000041723, + "networkEndTime": 215.4290000014007, + "finished": true, + "transferSize": 3256, + "resourceSize": 2966, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 172.43599999696016, + "networkRequestTime": 173.535000000149, + "networkEndTime": 216.54700000211596, + "finished": true, + "transferSize": 4592, + "resourceSize": 4302, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 172.67099999636412, + "networkRequestTime": 173.82699999958277, + "networkEndTime": 216.04399999976158, + "finished": true, + "transferSize": 3273, + "resourceSize": 2983, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 172.89499999955297, + "networkRequestTime": 174.0949999988079, + "networkEndTime": 216.81600000336766, + "finished": true, + "transferSize": 15920, + "resourceSize": 15628, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.07499999925494, + "networkRequestTime": 174.31199999898672, + "networkEndTime": 217.04399999976158, + "finished": true, + "transferSize": 11522, + "resourceSize": 11230, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/formatDate.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.22500000149012, + "networkRequestTime": 174.61199999973178, + "networkEndTime": 217.39900000020862, + "finished": true, + "transferSize": 5113, + "resourceSize": 4823, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.37800000235438, + "networkRequestTime": 174.937000002712, + "networkEndTime": 222.47599999979138, + "finished": true, + "transferSize": 26599, + "resourceSize": 26307, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.59599999710917, + "networkRequestTime": 175.20199999958277, + "networkEndTime": 224.47199999913573, + "finished": true, + "transferSize": 6332, + "resourceSize": 6042, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/styles/article-body.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 173.8339999988675, + "networkRequestTime": 175.4670000001788, + "networkEndTime": 230.29800000041723, + "finished": true, + "transferSize": 2477, + "resourceSize": 2187, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Article.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 174.05099999904633, + "networkRequestTime": 175.81399999931455, + "networkEndTime": 230.8480000011623, + "finished": true, + "transferSize": 13288, + "resourceSize": 12996, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 174.78099999949336, + "networkRequestTime": 176.11099999770522, + "networkEndTime": 231.12700000032783, + "finished": true, + "transferSize": 13726, + "resourceSize": 13434, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 174.9619999974966, + "networkRequestTime": 176.36500000208616, + "networkEndTime": 231.3259999975562, + "finished": true, + "transferSize": 6890, + "resourceSize": 6600, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/utils/passwordRules.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 175.1469999998808, + "networkRequestTime": 176.68400000035763, + "networkEndTime": 230.6680000014603, + "finished": true, + "transferSize": 3410, + "resourceSize": 3120, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Perfil.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 177, + "networkRequestTime": 177.43299999833107, + "networkEndTime": 233.214999999851, + "finished": true, + "transferSize": 4993, + "resourceSize": 4702, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/NotFound.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 179.035000000149, + "networkRequestTime": 180.03599999845028, + "networkEndTime": 234.62599999830127, + "finished": true, + "transferSize": 2228, + "resourceSize": 1938, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 181.81300000101328, + "networkRequestTime": 182.8209999985993, + "networkEndTime": 247.30499999970198, + "finished": true, + "transferSize": 108020, + "resourceSize": 107708, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCard.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 190.8909999988973, + "networkRequestTime": 193.02100000157952, + "networkEndTime": 246.08100000023842, + "finished": true, + "transferSize": 6902, + "resourceSize": 6611, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 191.2390000000596, + "networkRequestTime": 193.2989999987185, + "networkEndTime": 245.58799999952316, + "finished": true, + "transferSize": 3997, + "resourceSize": 3707, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 191.58100000023842, + "networkRequestTime": 193.50499999895692, + "networkEndTime": 246.75899999961257, + "finished": true, + "transferSize": 21741, + "resourceSize": 21449, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 191.76199999824166, + "networkRequestTime": 193.68699999898672, + "networkEndTime": 260.7289999984205, + "finished": true, + "transferSize": 21715, + "resourceSize": 21423, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/PageLayout.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 191.91400000080466, + "networkRequestTime": 194.07799999788404, + "networkEndTime": 256.54199999943376, + "finished": true, + "transferSize": 1789, + "resourceSize": 1499, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Button.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 197.84299999848008, + "networkRequestTime": 198.93899999931455, + "networkEndTime": 260.07699999958277, + "finished": true, + "transferSize": 2160, + "resourceSize": 1870, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/pages/Legal/Legal.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 206.88800000026822, + "networkRequestTime": 207.589999999851, + "networkEndTime": 261.3800000026822, + "finished": true, + "transferSize": 4112, + "resourceSize": 3822, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Input.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 210.46400000154972, + "networkRequestTime": 213.312000002712, + "networkEndTime": 261.0920000001788, + "finished": true, + "transferSize": 1768, + "resourceSize": 1478, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 210.937000002712, + "networkRequestTime": 213.56399999931455, + "networkEndTime": 261.625, + "finished": true, + "transferSize": 5342, + "resourceSize": 5052, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/AuthLayout.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 211.1439999975264, + "networkRequestTime": 213.78599999845028, + "networkEndTime": 265.94400000199676, + "finished": true, + "transferSize": 4726, + "resourceSize": 4435, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Badge.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 217.6279999986291, + "networkRequestTime": 218.8070000000298, + "networkEndTime": 264.79999999701977, + "finished": true, + "transferSize": 2103, + "resourceSize": 1813, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Avatar.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 222.1959999985993, + "networkRequestTime": 222.85200000181794, + "networkEndTime": 265.53999999910593, + "finished": true, + "transferSize": 1101, + "resourceSize": 812, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 224.3449999988079, + "networkRequestTime": 225.3690000027418, + "networkEndTime": 270.0950000025332, + "finished": true, + "transferSize": 24618, + "resourceSize": 24326, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/services/commentService.ts", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 224.61099999770522, + "networkRequestTime": 228.4809999987483, + "networkEndTime": 270.3779999986291, + "finished": true, + "transferSize": 2883, + "resourceSize": 2593, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 236.4780000001192, + "networkRequestTime": 240.44700000062585, + "networkEndTime": 270.6200000010431, + "finished": true, + "transferSize": 1998, + "resourceSize": 1708, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/Modal.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 238.45999999716878, + "networkRequestTime": 241.0430000014603, + "networkEndTime": 272.3310000002384, + "finished": true, + "transferSize": 2244, + "resourceSize": 1954, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 256.11500000208616, + "networkRequestTime": 257.6659999974072, + "networkEndTime": 273.1990000009537, + "finished": true, + "transferSize": 20091, + "resourceSize": 19799, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Navbar.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 256.36600000038743, + "networkRequestTime": 258.214999999851, + "networkEndTime": 274.1009999997914, + "finished": true, + "transferSize": 8587, + "resourceSize": 8296, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/layout/Footer.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 265.45499999821186, + "networkRequestTime": 267.8880000002682, + "networkEndTime": 278.570000000298, + "finished": true, + "transferSize": 5121, + "resourceSize": 4830, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/assets/seek-white.svg?import", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 266.00699999928474, + "networkRequestTime": 268.2719999998808, + "networkEndTime": 279.3260000012815, + "finished": true, + "transferSize": 10333, + "resourceSize": 10042, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 266.2169999964535, + "networkRequestTime": 268.5729999989271, + "networkEndTime": 278.2009999975562, + "finished": true, + "transferSize": 1532, + "resourceSize": 1242, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/components/ui/CommentItem.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 273.277000002563, + "networkRequestTime": 275.81899999827147, + "networkEndTime": 279.67500000074506, + "finished": true, + "transferSize": 4263, + "resourceSize": 3973, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 470.44199999794364, + "networkRequestTime": 471.2820000015199, + "networkEndTime": 473.9679999984801, + "finished": true, + "transferSize": 116617, + "resourceSize": 116344, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "Low", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "data:image/svg+xml,%3csvg%20id='Camada_1'%20data-name='Camada%201'%20xmlns='http://www.w3.org/2000/…", + "sessionTargetType": "page", + "protocol": "data", + "rendererStartTime": 473.597999997437, + "networkRequestTime": 473.597999997437, + "networkEndTime": 474.23000000044703, + "finished": true, + "transferSize": 0, + "resourceSize": 1581, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "Low", + "experimentalFromMainFrame": true + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/newsreader/files/newsreader-latin-wght-normal.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 503.6540000028908, + "networkRequestTime": 608.6579999998212, + "networkEndTime": 614.7609999999404, + "finished": true, + "transferSize": 58404, + "resourceSize": 58084, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/inter/files/inter-latin-wght-normal.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 516.9199999980628, + "networkRequestTime": 609.0449999980628, + "networkEndTime": 614.9439999982715, + "finished": true, + "transferSize": 48576, + "resourceSize": 48256, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/montserrat/files/montserrat-latin-wght-normal.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 527.4599999971688, + "networkRequestTime": 609.2799999974668, + "networkEndTime": 615.1550000011921, + "finished": true, + "transferSize": 38276, + "resourceSize": 37956, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/node_modules/@fontsource-variable/newsreader/files/newsreader-latin-wght-italic.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 527.5999999977648, + "networkRequestTime": 609.5369999967515, + "networkEndTime": 615.3469999991357, + "finished": true, + "transferSize": 64840, + "resourceSize": 64520, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 641.890000000596, + "networkRequestTime": 641.890000000596, + "networkEndTime": 677.9689999967813, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/auth/me/", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 644.4470000006258, + "networkRequestTime": 644.4470000006258, + "networkEndTime": 678.7649999968708, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 648.0969999991357, + "networkRequestTime": 648.0969999991357, + "networkEndTime": 679.3029999993742, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/auth/me/", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 650.6500000022352, + "networkRequestTime": 650.6500000022352, + "networkEndTime": 679.8990000002086, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 676.1349999979138, + "networkRequestTime": 676.6429999992251, + "networkEndTime": 678.5060000009835, + "finished": true, + "transferSize": 684, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:5173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 682.1499999985099, + "networkRequestTime": 682.6820000000298, + "networkEndTime": 685.0580000020564, + "finished": true, + "transferSize": 127, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + } + ], + "debugData": { + "type": "debugdata", + "networkStartTimeTs": 27098427285, + "initiators": [ + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 8, + "columnNumber": 46 + }, + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 41, + "columnNumber": 46 + }, + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 45, + "columnNumber": 69 + }, + { + "type": "script", + "url": "http://localhost:5173/", + "lineNumber": 3, + "columnNumber": 63 + }, + { + "type": "parser", + "url": "http://localhost:5173/", + "lineNumber": 54, + "columnNumber": 1 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 0, + "columnNumber": 228 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 1, + "columnNumber": 47 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 3, + "columnNumber": 16 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 4, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/main.tsx", + "lineNumber": 6, + "columnNumber": 51 + }, + { + "type": "script", + "url": "http://localhost:5173/@vite/client", + "lineNumber": 0, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "lineNumber": 0, + "columnNumber": 35 + }, + { + "type": "script", + "url": "http://localhost:5173/src/App.tsx", + "lineNumber": 0, + "columnNumber": 227 + }, + { + "type": "script", + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "lineNumber": 11, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 1, + "columnNumber": 45 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 2, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 3, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 4, + "columnNumber": 21 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 5, + "columnNumber": 21 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 6, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 7, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 8, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 9, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 10, + "columnNumber": 24 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 11, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 12, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 13, + "columnNumber": 31 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 14, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 15, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 16, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 17, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 18, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "lineNumber": 19, + "columnNumber": 34 + }, + { + "type": "script", + "url": "http://localhost:5173/src/services/authService.ts", + "lineNumber": 0, + "columnNumber": 16 + }, + { + "type": "script", + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "lineNumber": 2, + "columnNumber": 39 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "lineNumber": 1, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 2, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 3, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 4, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 5, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 6, + "columnNumber": 25 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Home.tsx", + "lineNumber": 7, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 2, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 3, + "columnNumber": 30 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 4, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "lineNumber": 5, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/News.tsx", + "lineNumber": 5, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/News.tsx", + "lineNumber": 7, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "lineNumber": 1, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/About/index.tsx", + "lineNumber": 1, + "columnNumber": 29 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/About/index.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "lineNumber": 2, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "lineNumber": 4, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "lineNumber": 7, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 3, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 4, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 6, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 7, + "columnNumber": 36 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 8, + "columnNumber": 31 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 9, + "columnNumber": 32 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 12, + "columnNumber": 34 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 13, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Article.tsx", + "lineNumber": 14, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "lineNumber": 5, + "columnNumber": 22 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "lineNumber": 10, + "columnNumber": 34 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "lineNumber": 11, + "columnNumber": 33 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "lineNumber": 21, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "lineNumber": 11, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/services/api.ts", + "lineNumber": 10, + "columnNumber": 18 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "lineNumber": 5, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "lineNumber": 0, + "columnNumber": 249 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "lineNumber": 1, + "columnNumber": 23 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "lineNumber": 0, + "columnNumber": 225 + }, + { + "type": "script", + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "lineNumber": 23, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "lineNumber": 0, + "columnNumber": 224 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "lineNumber": 2, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "lineNumber": 8, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "lineNumber": 0, + "columnNumber": 224 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "lineNumber": 10, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "lineNumber": 15, + "columnNumber": 28 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "lineNumber": 16, + "columnNumber": 27 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "lineNumber": 1, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "lineNumber": 2, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "lineNumber": 4, + "columnNumber": 31 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "lineNumber": 6, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "lineNumber": 6, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "lineNumber": 0, + "columnNumber": 244 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "lineNumber": 1, + "columnNumber": 7 + }, + { + "type": "script", + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "lineNumber": 7, + "columnNumber": 7 + } + ] + } + } + }, + "network-rtt": { + "id": "network-rtt", + "title": "Network Round Trip Times", + "description": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more about the Round Trip Time](https://hpbn.co/primer-on-latency-and-bandwidth/).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 0.0609, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "rtt", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:5173", + "rtt": 0.0609 + } + ], + "sortedBy": ["rtt"] + } + }, + "network-server-latency": { + "id": "network-server-latency", + "title": "Server Backend Latencies", + "description": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more about server response time](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 4.2930999999999955, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "serverResponseTime", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:5173", + "serverResponseTime": 4.2930999999999955 + } + ], + "sortedBy": ["serverResponseTime"] + } + }, + "main-thread-tasks": { + "id": "main-thread-tasks", + "title": "Tasks", + "description": "Lists the toplevel main thread tasks that executed during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "End Time" + } + ], + "items": [ + { + "duration": 11.666, + "startTime": 18.247 + }, + { + "duration": 15.239, + "startTime": 98.849 + }, + { + "duration": 5.393, + "startTime": 124.108 + }, + { + "duration": 91.693, + "startTime": 283.718 + }, + { + "duration": 24.986, + "startTime": 375.431 + }, + { + "duration": 71.135, + "startTime": 406.365 + }, + { + "duration": 19.716, + "startTime": 477.559 + }, + { + "duration": 112.272, + "startTime": 497.778 + }, + { + "duration": 13.979, + "startTime": 610.304 + }, + { + "duration": 24.895, + "startTime": 632.193 + }, + { + "duration": 9.583, + "startTime": 659.782 + }, + { + "duration": 8.103, + "startTime": 669.755 + }, + { + "duration": 24.6, + "startTime": 684.397 + }, + { + "duration": 12.847, + "startTime": 709.005 + }, + { + "duration": 6.956, + "startTime": 1424.445 + } + ] + } + }, + "metrics": { + "id": "metrics", + "title": "Metrics", + "description": "Collects all available metrics.", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 18303, + "numericUnit": "millisecond", + "details": { + "type": "debugdata", + "items": [ + { + "firstContentfulPaint": 10536, + "largestContentfulPaint": 18092, + "interactive": 18303, + "speedIndex": 10536, + "totalBlockingTime": 238, + "maxPotentialFID": 142, + "cumulativeLayoutShift": 0.176479, + "cumulativeLayoutShiftMainFrame": 0.176479, + "timeToFirstByte": 454, + "observedTimeOrigin": 0, + "observedTimeOriginTs": 27098425347, + "observedNavigationStart": 0, + "observedNavigationStartTs": 27098425347, + "observedFirstPaint": 408, + "observedFirstPaintTs": 27098833014, + "observedFirstContentfulPaint": 642, + "observedFirstContentfulPaintTs": 27099067702, + "observedFirstContentfulPaintAllFrames": 642, + "observedFirstContentfulPaintAllFramesTs": 27099067702, + "observedLargestContentfulPaint": 642, + "observedLargestContentfulPaintTs": 27099067702, + "observedLargestContentfulPaintAllFrames": 642, + "observedLargestContentfulPaintAllFramesTs": 27099067702, + "observedTraceEnd": 3060, + "observedTraceEndTs": 27101485137, + "observedLoad": 404, + "observedLoadTs": 27098829553, + "observedDomContentLoaded": 403, + "observedDomContentLoadedTs": 27098828451, + "observedCumulativeLayoutShift": 0.176479, + "observedCumulativeLayoutShiftMainFrame": 0.176479, + "observedFirstVisualChange": 407, + "observedFirstVisualChangeTs": 27098832347, + "observedLastVisualChange": 694, + "observedLastVisualChangeTs": 27099119347, + "observedSpeedIndex": 479, + "observedSpeedIndexTs": 27098903899 + }, + { + "lcpInvalidated": false + } + ] + } + }, + "resource-summary": { + "id": "resource-summary", + "title": "Resources Summary", + "description": "Aggregates all network requests and groups them by type", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Resource Type" + }, + { + "key": "requestCount", + "valueType": "numeric", + "label": "Requests" + }, + { + "key": "transferSize", + "valueType": "bytes", + "label": "Transfer Size" + } + ], + "items": [ + { + "resourceType": "total", + "label": "Total", + "requestCount": 104, + "transferSize": 2930513 + }, + { + "resourceType": "script", + "label": "Script", + "requestCount": 91, + "transferSize": 2599609 + }, + { + "resourceType": "font", + "label": "Font", + "requestCount": 4, + "transferSize": 210096 + }, + { + "resourceType": "image", + "label": "Image", + "requestCount": 1, + "transferSize": 116617 + }, + { + "resourceType": "document", + "label": "Document", + "requestCount": 1, + "transferSize": 2584 + }, + { + "resourceType": "other", + "label": "Other", + "requestCount": 7, + "transferSize": 1607 + }, + { + "resourceType": "stylesheet", + "label": "Stylesheet", + "requestCount": 0, + "transferSize": 0 + }, + { + "resourceType": "media", + "label": "Media", + "requestCount": 0, + "transferSize": 0 + }, + { + "resourceType": "third-party", + "label": "Third-party", + "requestCount": 1, + "transferSize": 0 + } + ] + } + }, + "third-party-summary": { + "id": "third-party-summary", + "title": "Minimize third-party usage", + "description": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn how to minimize third-party impact](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "Third-party code blocked the main thread for 0 ms", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "Third-Party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer Size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "blockingTime", + "granularity": 1, + "valueType": "ms", + "label": "Main-Thread Blocking Time", + "subItemsHeading": { + "key": "blockingTime" + } + } + ], + "items": [ + { + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0, + "entity": "vlibras.gov.br", + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0 + } + ] + } + } + ], + "isEntityGrouped": true + }, + "guidanceLevel": 1 + }, + "third-party-facades": { + "id": "third-party-facades", + "title": "Lazy load third-party resources with facades", + "description": "Some third-party embeds can be lazy loaded. Consider replacing them with a facade until they are required. [Learn how to defer third-parties with a facade](https://developer.chrome.com/docs/lighthouse/performance/third-party-facades/).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "TBT": 0 + }, + "guidanceLevel": 3 + }, + "largest-contentful-paint-element": { + "id": "largest-contentful-paint-element", + "title": "Largest Contentful Paint element", + "description": "This is the largest contentful element painted within the viewport. [Learn more about the Largest Contentful Paint element](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "18,090 ms", + "metricSavings": { + "LCP": 15600 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "Element" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 136, + "bottom": 341, + "left": 24, + "right": 388, + "width": 364, + "height": 205 + }, + "snippet": "

    ", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + } + ] + }, + { + "type": "table", + "headings": [ + { + "key": "phase", + "valueType": "text", + "label": "Phase" + }, + { + "key": "percent", + "valueType": "text", + "label": "% of LCP" + }, + { + "key": "timing", + "valueType": "ms", + "label": "Timing" + } + ], + "items": [ + { + "phase": "TTFB", + "timing": 454.2931, + "percent": "3%" + }, + { + "phase": "Load Delay", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Load Time", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Render Delay", + "timing": 17637.879300000015, + "percent": "97%" + } + ] + } + ] + }, + "guidanceLevel": 1 + }, + "lcp-lazy-loaded": { + "id": "lcp-lazy-loaded", + "title": "Largest Contentful Paint image was not lazily loaded", + "description": "Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. [Learn more about optimal lazy loading](https://web.dev/articles/lcp-lazy-loading).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 3 + }, + "layout-shifts": { + "id": "layout-shifts", + "title": "Avoid large layout shifts", + "description": "These are the largest layout shifts observed on the page. Each table item represents a single layout shift, and shows the element that shifted the most. Below each item are possible root causes that led to the layout shift. Some of these layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "1 layout shift found", + "metricSavings": { + "CLS": 0.176 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-1-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV", + "selector": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual", + "boundingRect": { + "top": 494, + "bottom": 674, + "left": 24, + "right": 388, + "width": 364, + "height": 180 + }, + "snippet": "
    ", + "nodeLabel": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual" + }, + "score": 0.176479 + } + ] + }, + "guidanceLevel": 2 + }, + "long-tasks": { + "id": "long-tasks", + "title": "Avoid long main-thread tasks", + "description": "Lists the longest tasks on the main thread, useful for identifying worst contributors to input delay. [Learn how to avoid long main-thread tasks](https://web.dev/articles/optimize-long-tasks)", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "7 long tasks found", + "metricSavings": { + "TBT": 250 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Duration" + } + ], + "items": [ + { + "url": "Unattributable", + "duration": 366.9999999999999, + "startTime": 722.2931 + }, + { + "url": "http://localhost:5173/", + "duration": 225, + "startTime": 1128.2930999999999 + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "duration": 142, + "startTime": 17962.87930000001 + }, + { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "duration": 100, + "startTime": 17862.87930000001 + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "duration": 100, + "startTime": 18104.87930000001 + }, + { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "duration": 98, + "startTime": 18204.87930000001 + }, + { + "url": "Unattributable", + "duration": 61, + "startTime": 661.2931 + } + ], + "sortedBy": ["duration"], + "skipSumming": ["startTime"], + "debugData": { + "type": "debugdata", + "urls": [ + "Unattributable", + "http://localhost:5173/", + "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "http://localhost:5173/src/components/ErrorFallback.tsx" + ], + "tasks": [ + { + "urlIndex": 0, + "startTime": 722.3, + "duration": 367, + "other": 367 + }, + { + "urlIndex": 1, + "startTime": 1128.3, + "duration": 225, + "other": 225, + "styleLayout": 0 + }, + { + "urlIndex": 2, + "startTime": 17962.9, + "duration": 142, + "other": 142 + }, + { + "urlIndex": 3, + "startTime": 17862.9, + "duration": 100, + "other": 100, + "scriptEvaluation": 0 + }, + { + "urlIndex": 2, + "startTime": 18104.9, + "duration": 100, + "other": 100 + }, + { + "urlIndex": 2, + "startTime": 18204.9, + "duration": 98, + "other": 98 + }, + { + "urlIndex": 0, + "startTime": 661.3, + "duration": 61, + "other": 61 + } + ] + } + }, + "guidanceLevel": 1 + }, + "non-composited-animations": { + "id": "non-composited-animations", + "title": "Avoid non-composited animations", + "description": "Animations which are not composited can be janky and increase CLS. [Learn how to avoid non-composited animations](https://developer.chrome.com/docs/lighthouse/performance/non-composited-animations/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "failureReason", + "valueType": "text" + }, + "label": "Element" + } + ], + "items": [] + }, + "guidanceLevel": 2 + }, + "unsized-images": { + "id": "unsized-images", + "title": "Image elements do not have explicit `width` and `height`", + "description": "Set an explicit width and height on image elements to reduce layout shifts and improve CLS. [Learn how to set image dimensions](https://web.dev/articles/optimize-cls#images_without_dimensions)", + "score": 0.5, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [ + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "node": { + "type": "node", + "lhId": "1-27-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,1,HEADER,0,DIV,0,A,0,IMG", + "selector": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img", + "boundingRect": { + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "snippet": "\"\"", + "nodeLabel": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img" + } + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "node": { + "type": "node", + "lhId": "1-28-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV,1,IMG", + "selector": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark", + "boundingRect": { + "top": 552, + "bottom": 616, + "left": 96, + "right": 316, + "width": 220, + "height": 64 + }, + "snippet": "\"\"", + "nodeLabel": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark" + } + }, + { + "url": "http://localhost:5173/src/assets/interpop-logo.svg", + "node": { + "type": "node", + "lhId": "1-29-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1800, + "bottom": 1840, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + } + } + ] + }, + "guidanceLevel": 4 + }, + "valid-source-maps": { + "id": "valid-source-maps", + "title": "Page has valid source maps", + "description": "Source maps translate minified code to the original source code. This helps developers debug in production. In addition, Lighthouse is able to provide further insights. Consider deploying source maps to take advantage of these benefits. [Learn more about source maps](https://developer.chrome.com/docs/devtools/javascript/source-maps/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "scriptUrl", + "valueType": "url", + "subItemsHeading": { + "key": "error" + }, + "label": "URL" + }, + { + "key": "sourceMapUrl", + "valueType": "url", + "label": "Map URL" + } + ], + "items": [ + { + "scriptUrl": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/passwordRules.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/formatDate.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/extractApiError.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/utils/categoryVariant.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/newsletterService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/commentService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/authService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/articleService.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/services/api.ts", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/router/AppRouter.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/router/AdminRoute.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Perfil.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/NotFound.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Newsletter.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/News.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Home.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/Register.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/Login.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/Article.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/About/index.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/main.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/contexts/AuthContext.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Modal.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Input.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Button.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Badge.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ui/Avatar.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/Navbar.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/Footer.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/ErrorFallback.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/assets/seek-white.svg?import", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/src/App.tsx", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "sourceMapUrl": "http://localhost:5173/node_modules/.vite/deps/axios.js.map", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/@vite/client", + "subItems": { + "type": "subitems", + "items": [] + } + }, + { + "scriptUrl": "http://localhost:5173/@react-refresh", + "subItems": { + "type": "subitems", + "items": [] + } + } + ] + } + }, + "prioritize-lcp-image": { + "id": "prioritize-lcp-image", + "title": "Preload Largest Contentful Paint image", + "description": "If the LCP element is dynamically added to the page, you should preload the image in order to improve LCP. [Learn more about preloading LCP elements](https://web.dev/articles/optimize-lcp#optimize_when_the_resource_is_discovered).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 4 + }, + "csp-xss": { + "id": "csp-xss", + "title": "Ensure CSP is effective against XSS attacks", + "description": "A strong Content Security Policy (CSP) significantly reduces the risk of cross-site scripting (XSS) attacks. [Learn how to use a CSP to prevent XSS](https://developer.chrome.com/docs/lighthouse/best-practices/csp-xss/)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No CSP found in enforcement mode" + } + ] + } + }, + "has-hsts": { + "id": "has-hsts", + "title": "Use a strong HSTS policy", + "description": "Deployment of the HSTS header significantly reduces the risk of downgrading HTTP connections and eavesdropping attacks. A rollout in stages, starting with a low max-age is recommended. [Learn more about using a strong HSTS policy.](https://developer.chrome.com/docs/lighthouse/best-practices/has-hsts)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No HSTS header found" + } + ] + } + }, + "origin-isolation": { + "id": "origin-isolation", + "title": "Ensure proper origin isolation with COOP", + "description": "The Cross-Origin-Opener-Policy (COOP) can be used to isolate the top-level window from other documents such as pop-ups. [Learn more about deploying the COOP header.](https://web.dev/articles/why-coop-coep#coop)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "description": "No COOP header found", + "severity": "High" + } + ] + } + }, + "clickjacking-mitigation": { + "id": "clickjacking-mitigation", + "title": "Mitigate clickjacking with XFO or CSP", + "description": "The `X-Frame-Options` (XFO) header or the `frame-ancestors` directive in the `Content-Security-Policy` (CSP) header control where a page can be embedded. These can mitigate clickjacking attacks by blocking some or all sites from embedding the page. [Learn more about mitigating clickjacking](https://developer.chrome.com/docs/lighthouse/best-practices/clickjacking-mitigation).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No frame control policy found" + } + ] + } + }, + "trusted-types-xss": { + "id": "trusted-types-xss", + "title": "Mitigate DOM-based XSS with Trusted Types", + "description": "The `require-trusted-types-for` directive in the `Content-Security-Policy` (CSP) header instructs user agents to control the data passed to DOM XSS sink functions. [Learn more about mitigating DOM-based XSS with Trusted Types](https://developer.chrome.com/docs/lighthouse/best-practices/trusted-types-xss).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No `Content-Security-Policy` header with Trusted Types directive found" + } + ] + } + }, + "script-treemap-data": { + "id": "script-treemap-data", + "title": "Script Treemap Data", + "description": "Used for treemap app", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "treemap-data", + "nodes": [ + { + "name": "http://localhost:5173/", + "resourceBytes": 329, + "encodedBytes": 329, + "unusedBytes": 28, + "children": [ + { + "name": "(inline) window.addEvent…", + "resourceBytes": 168, + "unusedBytes": 0 + }, + { + "name": "(inline) import { inject…", + "resourceBytes": 161, + "unusedBytes": 28 + } + ] + }, + { + "name": "http://localhost:5173/@react-refresh", + "resourceBytes": 111894, + "encodedBytes": 111894, + "unusedBytes": 6131, + "children": [ + { + "name": "@react-refresh", + "resourceBytes": 21291, + "unusedBytes": 6131 + }, + { + "name": "(unmapped)", + "resourceBytes": 90603 + } + ] + }, + { + "name": "http://localhost:5173/@vite/client", + "resourceBytes": 209821, + "encodedBytes": 209821, + "unusedBytes": 22340, + "children": [ + { + "name": "client", + "resourceBytes": 39552, + "unusedBytes": 22340 + }, + { + "name": "(unmapped)", + "resourceBytes": 170269 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "resourceBytes": 3478, + "encodedBytes": 3478, + "unusedBytes": 272, + "children": [ + { + "name": "env.mjs", + "resourceBytes": 603, + "unusedBytes": 272 + }, + { + "name": "(unmapped)", + "resourceBytes": 2875 + } + ] + }, + { + "name": "http://localhost:5173/src/main.tsx", + "resourceBytes": 2040, + "encodedBytes": 2040, + "children": [ + { + "name": "main.tsx", + "resourceBytes": 751 + }, + { + "name": "(unmapped)", + "resourceBytes": 1289 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "resourceBytes": 38303, + "encodedBytes": 38303, + "unusedBytes": 17278, + "children": [ + { + "name": "../../react", + "resourceBytes": 35028, + "unusedBytes": 17278, + "children": [ + { + "name": "cjs/react.development.js", + "resourceBytes": 34982, + "unusedBytes": 17278 + }, + { + "name": "index.js", + "resourceBytes": 46 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 3275 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "resourceBytes": 7614, + "encodedBytes": 7614, + "children": [ + { + "name": "chunk-B-1-B7_t.js?v=696798f9", + "resourceBytes": 1407 + }, + { + "name": "(unmapped)", + "resourceBytes": 6207 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "resourceBytes": 820938, + "encodedBytes": 820982, + "unusedBytes": 310960, + "children": [ + { + "name": "../..", + "resourceBytes": 759489, + "unusedBytes": 310960, + "children": [ + { + "name": "scheduler", + "resourceBytes": 8551, + "unusedBytes": 1969, + "children": [ + { + "name": "cjs/scheduler.development.js", + "resourceBytes": 8501, + "unusedBytes": 1969 + }, + { + "name": "index.js", + "resourceBytes": 50 + } + ] + }, + { + "name": "react-dom", + "resourceBytes": 750938, + "unusedBytes": 308991, + "children": [ + { + "name": "cjs/react-dom-client.development.js", + "resourceBytes": 750881, + "unusedBytes": 308991 + }, + { + "name": "client.js", + "resourceBytes": 57 + } + ] + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 61449 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "resourceBytes": 14630, + "encodedBytes": 14630, + "unusedBytes": 11676, + "children": [ + { + "name": "../../react-dom", + "resourceBytes": 13284, + "unusedBytes": 11676, + "children": [ + { + "name": "cjs/react-dom.development.js", + "resourceBytes": 13234, + "unusedBytes": 11676 + }, + { + "name": "index.js", + "resourceBytes": 50 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 1346 + } + ] + }, + { + "name": "http://localhost:5173/src/styles/global.css", + "resourceBytes": 16411, + "encodedBytes": 16581, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/App.tsx", + "resourceBytes": 2239, + "encodedBytes": 2239, + "children": [ + { + "name": "App.tsx", + "resourceBytes": 353 + }, + { + "name": "(unmapped)", + "resourceBytes": 1886 + } + ] + }, + { + "name": "http://localhost:5173/src/router/AppRouter.tsx", + "resourceBytes": 21313, + "encodedBytes": 21319, + "unusedBytes": 502, + "children": [ + { + "name": "AppRouter.tsx", + "resourceBytes": 5421, + "unusedBytes": 502 + }, + { + "name": "(unmapped)", + "resourceBytes": 15892 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "resourceBytes": 398951, + "encodedBytes": 398955, + "unusedBytes": 322162, + "children": [ + { + "name": "../..", + "resourceBytes": 365753, + "unusedBytes": 322162, + "children": [ + { + "name": "react-router/dist/development", + "resourceBytes": 357318, + "unusedBytes": 314680, + "children": [ + { + "name": "chunk-4N6VE7H7.mjs", + "resourceBytes": 290362, + "unusedBytes": 248662 + }, + { + "name": "chunk-RJYABSBD.mjs", + "resourceBytes": 61391, + "unusedBytes": 60496 + }, + { + "name": "dom-export.mjs", + "resourceBytes": 5565, + "unusedBytes": 5522 + } + ] + }, + { + "name": "cookie/dist/index.js", + "resourceBytes": 4071, + "unusedBytes": 3354 + }, + { + "name": "set-cookie-parser/lib/set-cookie.js", + "resourceBytes": 4364, + "unusedBytes": 4128 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 33198 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "resourceBytes": 3150, + "encodedBytes": 3150, + "unusedBytes": 1711, + "children": [ + { + "name": "../../react-error-boundary/dist/react-error-boundary.js", + "resourceBytes": 2461, + "unusedBytes": 1711 + }, + { + "name": "(unmapped)", + "resourceBytes": 689 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ErrorFallback.tsx", + "resourceBytes": 9515, + "encodedBytes": 9524, + "unusedBytes": 1542, + "children": [ + { + "name": "ErrorFallback.tsx", + "resourceBytes": 1997, + "unusedBytes": 1542 + }, + { + "name": "(unmapped)", + "resourceBytes": 7518 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ErrorFallback.css", + "resourceBytes": 2833, + "encodedBytes": 2833, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "resourceBytes": 10573, + "encodedBytes": 10573, + "unusedBytes": 869, + "children": [ + { + "name": "../../react", + "resourceBytes": 9137, + "unusedBytes": 869, + "children": [ + { + "name": "cjs/react-jsx-dev-runtime.development.js", + "resourceBytes": 9075, + "unusedBytes": 869 + }, + { + "name": "jsx-dev-runtime.js", + "resourceBytes": 62 + } + ] + }, + { + "name": "(unmapped)", + "resourceBytes": 1436 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Home.tsx", + "resourceBytes": 21978, + "encodedBytes": 22007, + "unusedBytes": 228, + "children": [ + { + "name": "Home.tsx", + "resourceBytes": 4680, + "unusedBytes": 228 + }, + { + "name": "(unmapped)", + "resourceBytes": 17298 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "resourceBytes": 4500, + "encodedBytes": 4501, + "children": [ + { + "name": "PageLayout.tsx", + "resourceBytes": 836 + }, + { + "name": "(unmapped)", + "resourceBytes": 3664 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/Navbar.tsx", + "resourceBytes": 21435, + "encodedBytes": 21449, + "unusedBytes": 612, + "children": [ + { + "name": "Navbar.tsx", + "resourceBytes": 4831, + "unusedBytes": 612 + }, + { + "name": "(unmapped)", + "resourceBytes": 16604 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Button.tsx", + "resourceBytes": 3460, + "encodedBytes": 3460, + "children": [ + { + "name": "Button.tsx", + "resourceBytes": 552 + }, + { + "name": "(unmapped)", + "resourceBytes": 2908 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Button.css", + "resourceBytes": 1870, + "encodedBytes": 1870, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/contexts/AuthContext.tsx", + "resourceBytes": 11441, + "encodedBytes": 11454, + "unusedBytes": 347, + "children": [ + { + "name": "AuthContext.tsx", + "resourceBytes": 1556, + "unusedBytes": 347 + }, + { + "name": "(unmapped)", + "resourceBytes": 9885 + } + ] + }, + { + "name": "http://localhost:5173/src/services/authService.ts", + "resourceBytes": 5512, + "encodedBytes": 5520, + "unusedBytes": 742, + "children": [ + { + "name": "authService.ts", + "resourceBytes": 966, + "unusedBytes": 742 + }, + { + "name": "(unmapped)", + "resourceBytes": 4546 + } + ] + }, + { + "name": "http://localhost:5173/src/services/api.ts", + "resourceBytes": 6673, + "encodedBytes": 6785, + "unusedBytes": 152, + "children": [ + { + "name": "api.ts", + "resourceBytes": 946, + "unusedBytes": 152 + }, + { + "name": "(unmapped)", + "resourceBytes": 5727 + } + ] + }, + { + "name": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "resourceBytes": 107708, + "encodedBytes": 107708, + "children": [ + { + "name": "../../axios", + "resourceBytes": 78466, + "children": [ + { + "name": "lib", + "resourceBytes": 78262, + "children": [ + { + "name": "helpers", + "resourceBytes": 23165, + "children": [ + { + "name": "bind.js", + "resourceBytes": 96 + }, + { + "name": "parseHeaders.js", + "resourceBytes": 794, + "unusedBytes": 465 + }, + { + "name": "sanitizeHeaderValue.js", + "resourceBytes": 1263, + "unusedBytes": 43 + }, + { + "name": "toFormData.js", + "resourceBytes": 3780, + "unusedBytes": 583 + }, + { + "name": "AxiosURLSearchParams.js", + "resourceBytes": 746, + "unusedBytes": 227 + }, + { + "name": "buildURL.js", + "resourceBytes": 840 + }, + { + "name": "toURLEncodedForm.js", + "resourceBytes": 364, + "unusedBytes": 363 + }, + { + "name": "formDataToJSON.js", + "resourceBytes": 1371, + "unusedBytes": 1368 + }, + { + "name": "parseProtocol.js", + "resourceBytes": 117 + }, + { + "name": "speedometer.js", + "resourceBytes": 774, + "unusedBytes": 773 + }, + { + "name": "throttle.js", + "resourceBytes": 578, + "unusedBytes": 577 + }, + { + "name": "progressEventReducer.js", + "resourceBytes": 1063, + "unusedBytes": 980 + }, + { + "name": "isURLSameOrigin.js", + "resourceBytes": 400, + "unusedBytes": 10 + }, + { + "name": "cookies.js", + "resourceBytes": 1081, + "unusedBytes": 997 + }, + { + "name": "isAbsoluteURL.js", + "resourceBytes": 123 + }, + { + "name": "combineURLs.js", + "resourceBytes": 149 + }, + { + "name": "resolveConfig.js", + "resourceBytes": 1970, + "unusedBytes": 382 + }, + { + "name": "composeSignals.js", + "resourceBytes": 1044, + "unusedBytes": 1021 + }, + { + "name": "trackStream.js", + "resourceBytes": 1299, + "unusedBytes": 1222 + }, + { + "name": "estimateDataURLDecodedBytes.js", + "resourceBytes": 1616, + "unusedBytes": 1615 + }, + { + "name": "validator.js", + "resourceBytes": 1900, + "unusedBytes": 251 + }, + { + "name": "spread.js", + "resourceBytes": 97, + "unusedBytes": 96 + }, + { + "name": "isAxiosError.js", + "resourceBytes": 110, + "unusedBytes": 109 + }, + { + "name": "HttpStatusCode.js", + "resourceBytes": 1590 + } + ], + "unusedBytes": 11082 + }, + { + "name": "utils.js", + "resourceBytes": 11433, + "unusedBytes": 2731 + }, + { + "name": "core", + "resourceBytes": 21155, + "unusedBytes": 6123, + "children": [ + { + "name": "AxiosHeaders.js", + "resourceBytes": 6322, + "unusedBytes": 2075 + }, + { + "name": "AxiosError.js", + "resourceBytes": 3548, + "unusedBytes": 2299 + }, + { + "name": "InterceptorManager.js", + "resourceBytes": 508, + "unusedBytes": 112 + }, + { + "name": "transformData.js", + "resourceBytes": 368 + }, + { + "name": "settle.js", + "resourceBytes": 434, + "unusedBytes": 433 + }, + { + "name": "buildFullPath.js", + "resourceBytes": 246 + }, + { + "name": "mergeConfig.js", + "resourceBytes": 3020 + }, + { + "name": "dispatchRequest.js", + "resourceBytes": 1352, + "unusedBytes": 307 + }, + { + "name": "Axios.js", + "resourceBytes": 5357, + "unusedBytes": 897 + } + ] + }, + { + "name": "defaults", + "resourceBytes": 3475, + "children": [ + { + "name": "transitional.js", + "resourceBytes": 148 + }, + { + "name": "index.js", + "resourceBytes": 3327, + "unusedBytes": 967 + } + ], + "unusedBytes": 967 + }, + { + "name": "platform", + "resourceBytes": 918, + "children": [ + { + "name": "browser", + "resourceBytes": 327, + "children": [ + { + "name": "index.js", + "resourceBytes": 153 + }, + { + "name": "classes", + "resourceBytes": 174, + "children": [ + { + "name": "URLSearchParams.js", + "resourceBytes": 81 + }, + { + "name": "FormData.js", + "resourceBytes": 51 + }, + { + "name": "Blob.js", + "resourceBytes": 42 + } + ] + } + ] + }, + { + "name": "common/utils.js", + "resourceBytes": 526 + }, + { + "name": "index.js", + "resourceBytes": 65 + } + ] + }, + { + "name": "cancel", + "resourceBytes": 1914, + "children": [ + { + "name": "isCancel.js", + "resourceBytes": 69 + }, + { + "name": "CanceledError.js", + "resourceBytes": 240, + "unusedBytes": 185 + }, + { + "name": "CancelToken.js", + "resourceBytes": 1605, + "unusedBytes": 1547 + } + ], + "unusedBytes": 1732 + }, + { + "name": "adapters", + "resourceBytes": 15020, + "unusedBytes": 8327, + "children": [ + { + "name": "xhr.js", + "resourceBytes": 4053, + "unusedBytes": 1092 + }, + { + "name": "fetch.js", + "resourceBytes": 9343, + "unusedBytes": 7083 + }, + { + "name": "adapters.js", + "resourceBytes": 1624, + "unusedBytes": 152 + } + ] + }, + { + "name": "env/data.js", + "resourceBytes": 26 + }, + { + "name": "axios.js", + "resourceBytes": 1156, + "unusedBytes": 144 + } + ], + "unusedBytes": 31106 + }, + { + "name": "index.js", + "resourceBytes": 204 + } + ], + "unusedBytes": 31106 + }, + { + "name": "(unmapped)", + "resourceBytes": 29242 + } + ], + "unusedBytes": 31106 + }, + { + "name": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "resourceBytes": 19771, + "encodedBytes": 19799, + "unusedBytes": 3800, + "children": [ + { + "name": "NavbarUserMenu.tsx", + "resourceBytes": 4099, + "unusedBytes": 3800 + }, + { + "name": "(unmapped)", + "resourceBytes": 15672 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Avatar.tsx", + "resourceBytes": 4292, + "encodedBytes": 4302, + "unusedBytes": 326, + "children": [ + { + "name": "Avatar.tsx", + "resourceBytes": 374, + "unusedBytes": 326 + }, + { + "name": "(unmapped)", + "resourceBytes": 3918 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Avatar.css", + "resourceBytes": 807, + "encodedBytes": 812, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "resourceBytes": 401, + "encodedBytes": 401, + "children": [ + { + "name": "interpop-logo.svg?import", + "resourceBytes": 47 + }, + { + "name": "(unmapped)", + "resourceBytes": 354 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/Navbar.css", + "resourceBytes": 8274, + "encodedBytes": 8296, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/layout/Footer.tsx", + "resourceBytes": 21395, + "encodedBytes": 21423, + "unusedBytes": 352, + "children": [ + { + "name": "Footer.tsx", + "resourceBytes": 4938, + "unusedBytes": 352 + }, + { + "name": "(unmapped)", + "resourceBytes": 16457 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "resourceBytes": 5048, + "encodedBytes": 5052, + "children": [ + { + "name": "DevelopedBy.tsx", + "resourceBytes": 888 + }, + { + "name": "(unmapped)", + "resourceBytes": 4160 + } + ] + }, + { + "name": "http://localhost:5173/src/assets/seek-white.svg?import", + "resourceBytes": 10042, + "encodedBytes": 10042, + "children": [ + { + "name": "seek-white.svg?import", + "resourceBytes": 1736 + }, + { + "name": "(unmapped)", + "resourceBytes": 8306 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "resourceBytes": 1240, + "encodedBytes": 1242, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/services/newsletterService.ts", + "resourceBytes": 1073, + "encodedBytes": 1073, + "unusedBytes": 128, + "children": [ + { + "name": "newsletterService.ts", + "resourceBytes": 260, + "unusedBytes": 128 + }, + { + "name": "(unmapped)", + "resourceBytes": 813 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/Footer.css", + "resourceBytes": 4810, + "encodedBytes": 4830, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/layout/PageLayout.css", + "resourceBytes": 1401, + "encodedBytes": 1499, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "resourceBytes": 12334, + "encodedBytes": 12340, + "unusedBytes": 1834, + "children": [ + { + "name": "NewsCard.tsx", + "resourceBytes": 2910, + "unusedBytes": 1834 + }, + { + "name": "(unmapped)", + "resourceBytes": 9424 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Badge.tsx", + "resourceBytes": 2983, + "encodedBytes": 2983, + "unusedBytes": 198, + "children": [ + { + "name": "Badge.tsx", + "resourceBytes": 462, + "unusedBytes": 198 + }, + { + "name": "(unmapped)", + "resourceBytes": 2521 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Badge.css", + "resourceBytes": 1804, + "encodedBytes": 1813, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/utils/categoryVariant.ts", + "resourceBytes": 2270, + "encodedBytes": 2272, + "unusedBytes": 143, + "children": [ + { + "name": "categoryVariant.ts", + "resourceBytes": 345, + "unusedBytes": 143 + }, + { + "name": "(unmapped)", + "resourceBytes": 1925 + } + ] + }, + { + "name": "http://localhost:5173/src/utils/formatDate.ts", + "resourceBytes": 4815, + "encodedBytes": 4823, + "unusedBytes": 707, + "children": [ + { + "name": "formatDate.ts", + "resourceBytes": 732, + "unusedBytes": 707 + }, + { + "name": "(unmapped)", + "resourceBytes": 4083 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCard.css", + "resourceBytes": 6585, + "encodedBytes": 6611, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "resourceBytes": 35307, + "encodedBytes": 35586, + "unusedBytes": 6216, + "children": [ + { + "name": "NewsCarousel.tsx", + "resourceBytes": 6862, + "unusedBytes": 6216 + }, + { + "name": "(unmapped)", + "resourceBytes": 28445 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "resourceBytes": 3679, + "encodedBytes": 3707, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/services/articleService.ts", + "resourceBytes": 9433, + "encodedBytes": 9548, + "unusedBytes": 1143, + "children": [ + { + "name": "articleService.ts", + "resourceBytes": 1440, + "unusedBytes": 1143 + }, + { + "name": "(unmapped)", + "resourceBytes": 7993 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Home.css", + "resourceBytes": 9077, + "encodedBytes": 9192, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/News.tsx", + "resourceBytes": 30992, + "encodedBytes": 31011, + "unusedBytes": 6157, + "children": [ + { + "name": "News.tsx", + "resourceBytes": 7132, + "unusedBytes": 6157 + }, + { + "name": "(unmapped)", + "resourceBytes": 23860 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/News.css", + "resourceBytes": 1590, + "encodedBytes": 1592, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Newsletter.tsx", + "resourceBytes": 18778, + "encodedBytes": 18826, + "unusedBytes": 4009, + "children": [ + { + "name": "Newsletter.tsx", + "resourceBytes": 4663, + "unusedBytes": 4009 + }, + { + "name": "(unmapped)", + "resourceBytes": 14115 + } + ] + }, + { + "name": "http://localhost:5173/src/utils/extractApiError.ts", + "resourceBytes": 6805, + "encodedBytes": 6820, + "unusedBytes": 998, + "children": [ + { + "name": "extractApiError.ts", + "resourceBytes": 1006, + "unusedBytes": 998 + }, + { + "name": "(unmapped)", + "resourceBytes": 5799 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Newsletter.css", + "resourceBytes": 4683, + "encodedBytes": 4707, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/About/index.tsx", + "resourceBytes": 5492, + "encodedBytes": 5500, + "unusedBytes": 649, + "children": [ + { + "name": "index.tsx", + "resourceBytes": 1043, + "unusedBytes": 649 + }, + { + "name": "(unmapped)", + "resourceBytes": 4449 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "resourceBytes": 15372, + "encodedBytes": 15442, + "unusedBytes": 3316, + "children": [ + { + "name": "AboutContent.tsx", + "resourceBytes": 3404, + "unusedBytes": 3316 + }, + { + "name": "(unmapped)", + "resourceBytes": 11968 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/About/About.css", + "resourceBytes": 3313, + "encodedBytes": 3627, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "resourceBytes": 4475, + "encodedBytes": 4475, + "unusedBytes": 586, + "children": [ + { + "name": "Termos.tsx", + "resourceBytes": 944, + "unusedBytes": 586 + }, + { + "name": "(unmapped)", + "resourceBytes": 3531 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "resourceBytes": 74854, + "encodedBytes": 75419, + "unusedBytes": 22267, + "children": [ + { + "name": "LegalContent.tsx", + "resourceBytes": 22403, + "unusedBytes": 22267 + }, + { + "name": "(unmapped)", + "resourceBytes": 52451 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Legal/Legal.css", + "resourceBytes": 3820, + "encodedBytes": 3822, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "resourceBytes": 4571, + "encodedBytes": 4572, + "unusedBytes": 606, + "children": [ + { + "name": "Privacidade.tsx", + "resourceBytes": 969, + "unusedBytes": 606 + }, + { + "name": "(unmapped)", + "resourceBytes": 3602 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Article.tsx", + "resourceBytes": 34893, + "encodedBytes": 34910, + "unusedBytes": 7195, + "children": [ + { + "name": "Article.tsx", + "resourceBytes": 8566, + "unusedBytes": 7195 + }, + { + "name": "(unmapped)", + "resourceBytes": 26327 + } + ] + }, + { + "name": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "resourceBytes": 15628, + "encodedBytes": 15628, + "unusedBytes": 1091, + "children": [ + { + "name": "ArticleShareBar.tsx", + "resourceBytes": 3998, + "unusedBytes": 1091 + }, + { + "name": "(unmapped)", + "resourceBytes": 11630 + } + ] + }, + { + "name": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "resourceBytes": 11195, + "encodedBytes": 11230, + "unusedBytes": 1800, + "children": [ + { + "name": "ArticleAdminActions.tsx", + "resourceBytes": 2048, + "unusedBytes": 1800 + }, + { + "name": "(unmapped)", + "resourceBytes": 9147 + } + ] + }, + { + "name": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "resourceBytes": 26250, + "encodedBytes": 26307, + "unusedBytes": 4822, + "children": [ + { + "name": "ArticleComments.tsx", + "resourceBytes": 5244, + "unusedBytes": 4822 + }, + { + "name": "(unmapped)", + "resourceBytes": 21006 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "resourceBytes": 24305, + "encodedBytes": 24326, + "unusedBytes": 4983, + "children": [ + { + "name": "CommentItem.tsx", + "resourceBytes": 5814, + "unusedBytes": 4983 + }, + { + "name": "(unmapped)", + "resourceBytes": 18491 + } + ] + }, + { + "name": "http://localhost:5173/src/services/commentService.ts", + "resourceBytes": 2593, + "encodedBytes": 2593, + "unusedBytes": 292, + "children": [ + { + "name": "commentService.ts", + "resourceBytes": 429, + "unusedBytes": 292 + }, + { + "name": "(unmapped)", + "resourceBytes": 2164 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/CommentItem.css", + "resourceBytes": 3973, + "encodedBytes": 3973, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "resourceBytes": 6032, + "encodedBytes": 6042, + "unusedBytes": 963, + "children": [ + { + "name": "renderArticleBody.tsx", + "resourceBytes": 971, + "unusedBytes": 963 + }, + { + "name": "(unmapped)", + "resourceBytes": 5061 + } + ] + }, + { + "name": "http://localhost:5173/src/styles/article-body.css", + "resourceBytes": 2183, + "encodedBytes": 2187, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Article.css", + "resourceBytes": 12725, + "encodedBytes": 12996, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/Login.tsx", + "resourceBytes": 12785, + "encodedBytes": 12805, + "unusedBytes": 2362, + "children": [ + { + "name": "Login.tsx", + "resourceBytes": 3151, + "unusedBytes": 2362 + }, + { + "name": "(unmapped)", + "resourceBytes": 9634 + } + ] + }, + { + "name": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "resourceBytes": 19215, + "encodedBytes": 19226, + "unusedBytes": 3527, + "children": [ + { + "name": "AuthLayout.tsx", + "resourceBytes": 4401, + "unusedBytes": 3527 + }, + { + "name": "(unmapped)", + "resourceBytes": 14814 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Modal.tsx", + "resourceBytes": 13421, + "encodedBytes": 13434, + "unusedBytes": 2161, + "children": [ + { + "name": "Modal.tsx", + "resourceBytes": 2915, + "unusedBytes": 2161 + }, + { + "name": "(unmapped)", + "resourceBytes": 10506 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Modal.css", + "resourceBytes": 1954, + "encodedBytes": 1954, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/layout/AuthLayout.css", + "resourceBytes": 4408, + "encodedBytes": 4435, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/components/ui/Input.tsx", + "resourceBytes": 4149, + "encodedBytes": 4149, + "unusedBytes": 486, + "children": [ + { + "name": "Input.tsx", + "resourceBytes": 750, + "unusedBytes": 486 + }, + { + "name": "(unmapped)", + "resourceBytes": 3399 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/Input.css", + "resourceBytes": 1478, + "encodedBytes": 1478, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/Auth.css", + "resourceBytes": 2958, + "encodedBytes": 2966, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/Register.tsx", + "resourceBytes": 27647, + "encodedBytes": 27667, + "unusedBytes": 5430, + "children": [ + { + "name": "Register.tsx", + "resourceBytes": 6616, + "unusedBytes": 5430 + }, + { + "name": "(unmapped)", + "resourceBytes": 21031 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "resourceBytes": 6578, + "encodedBytes": 6600, + "unusedBytes": 775, + "children": [ + { + "name": "PasswordChecklist.tsx", + "resourceBytes": 1125, + "unusedBytes": 775 + }, + { + "name": "(unmapped)", + "resourceBytes": 5453 + } + ] + }, + { + "name": "http://localhost:5173/src/utils/passwordRules.ts", + "resourceBytes": 3112, + "encodedBytes": 3120, + "unusedBytes": 207, + "children": [ + { + "name": "passwordRules.ts", + "resourceBytes": 608, + "unusedBytes": 207 + }, + { + "name": "(unmapped)", + "resourceBytes": 2504 + } + ] + }, + { + "name": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "resourceBytes": 1682, + "encodedBytes": 1708, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "resourceBytes": 12527, + "encodedBytes": 12541, + "unusedBytes": 2385, + "children": [ + { + "name": "ForgotPassword.tsx", + "resourceBytes": 3108, + "unusedBytes": 2385 + }, + { + "name": "(unmapped)", + "resourceBytes": 9419 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "resourceBytes": 17916, + "encodedBytes": 17930, + "unusedBytes": 3630, + "children": [ + { + "name": "ResetPassword.tsx", + "resourceBytes": 4518, + "unusedBytes": 3630 + }, + { + "name": "(unmapped)", + "resourceBytes": 13398 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Perfil.tsx", + "resourceBytes": 55208, + "encodedBytes": 55540, + "unusedBytes": 11009, + "children": [ + { + "name": "Perfil.tsx", + "resourceBytes": 11720, + "unusedBytes": 11009 + }, + { + "name": "(unmapped)", + "resourceBytes": 43488 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/Perfil.css", + "resourceBytes": 4623, + "encodedBytes": 4702, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/NotFound.tsx", + "resourceBytes": 10252, + "encodedBytes": 10287, + "unusedBytes": 1684, + "children": [ + { + "name": "NotFound.tsx", + "resourceBytes": 1972, + "unusedBytes": 1684 + }, + { + "name": "(unmapped)", + "resourceBytes": 8280 + } + ] + }, + { + "name": "http://localhost:5173/src/pages/NotFound.css", + "resourceBytes": 1938, + "encodedBytes": 1938, + "unusedBytes": 37 + }, + { + "name": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "resourceBytes": 12421, + "encodedBytes": 12442, + "unusedBytes": 2049, + "children": [ + { + "name": "Unsubscribe.tsx", + "resourceBytes": 2811, + "unusedBytes": 2049 + }, + { + "name": "(unmapped)", + "resourceBytes": 9610 + } + ] + }, + { + "name": "http://localhost:5173/src/router/AdminRoute.tsx", + "resourceBytes": 5913, + "encodedBytes": 5928, + "unusedBytes": 534, + "children": [ + { + "name": "AdminRoute.tsx", + "resourceBytes": 968, + "unusedBytes": 534 + }, + { + "name": "(unmapped)", + "resourceBytes": 4945 + } + ] + }, + { + "name": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "resourceBytes": 10074, + "encodedBytes": 10086, + "unusedBytes": 234, + "children": [ + { + "name": "ScrollToHashOrTop.tsx", + "resourceBytes": 1530, + "unusedBytes": 234 + }, + { + "name": "(unmapped)", + "resourceBytes": 8544 + } + ] + } + ] + } + }, + "accesskeys": { + "id": "accesskeys", + "title": "`[accesskey]` values are unique", + "description": "Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more about access keys](https://dequeuniversity.com/rules/axe/4.10/accesskeys).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-allowed-attr": { + "id": "aria-allowed-attr", + "title": "`[aria-*]` attributes match their roles", + "description": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn how to match ARIA attributes to their roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-allowed-role": { + "id": "aria-allowed-role", + "title": "Uses ARIA roles only on compatible elements", + "description": "Many HTML elements can only be assigned certain ARIA roles. Using ARIA roles where they are not allowed can interfere with the accessibility of the web page. [Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-command-name": { + "id": "aria-command-name", + "title": "`button`, `link`, and `menuitem` elements have accessible names", + "description": "When an element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to make command elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-command-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-conditional-attr": { + "id": "aria-conditional-attr", + "title": "ARIA attributes are used as specified for the element's role", + "description": "Some ARIA attributes are only allowed on an element under certain conditions. [Learn more about conditional ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-conditional-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-deprecated-role": { + "id": "aria-deprecated-role", + "title": "Deprecated ARIA roles were not used", + "description": "Deprecated ARIA roles may not be processed correctly by assistive technology. [Learn more about deprecated ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-deprecated-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-dialog-name": { + "id": "aria-dialog-name", + "title": "Elements with `role=\"dialog\"` or `role=\"alertdialog\"` have accessible names.", + "description": "ARIA dialog elements without accessible names may prevent screen readers users from discerning the purpose of these elements. [Learn how to make ARIA dialog elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-dialog-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-hidden-body": { + "id": "aria-hidden-body", + "title": "`[aria-hidden=\"true\"]` is not present on the document ``", + "description": "Assistive technologies, like screen readers, work inconsistently when `aria-hidden=\"true\"` is set on the document ``. [Learn how `aria-hidden` affects the document body](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-body).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-hidden-focus": { + "id": "aria-hidden-focus", + "title": "`[aria-hidden=\"true\"]` elements do not contain focusable descendents", + "description": "Focusable descendents within an `[aria-hidden=\"true\"]` element prevent those interactive elements from being available to users of assistive technologies like screen readers. [Learn how `aria-hidden` affects focusable elements](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-input-field-name": { + "id": "aria-input-field-name", + "title": "ARIA input fields have accessible names", + "description": "When an input field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about input field labels](https://dequeuniversity.com/rules/axe/4.10/aria-input-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-meter-name": { + "id": "aria-meter-name", + "title": "ARIA `meter` elements have accessible names", + "description": "When a meter element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `meter` elements](https://dequeuniversity.com/rules/axe/4.10/aria-meter-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-progressbar-name": { + "id": "aria-progressbar-name", + "title": "ARIA `progressbar` elements have accessible names", + "description": "When a `progressbar` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to label `progressbar` elements](https://dequeuniversity.com/rules/axe/4.10/aria-progressbar-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-prohibited-attr": { + "id": "aria-prohibited-attr", + "title": "Elements use only permitted ARIA attributes", + "description": "Using ARIA attributes in roles where they are prohibited can mean that important information is not communicated to users of assistive technologies. [Learn more about prohibited ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-prohibited-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-attr": { + "id": "aria-required-attr", + "title": "`[role]`s have all required `[aria-*]` attributes", + "description": "Some ARIA roles have required attributes that describe the state of the element to screen readers. [Learn more about roles and required attributes](https://dequeuniversity.com/rules/axe/4.10/aria-required-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-children": { + "id": "aria-required-children", + "title": "Elements with an ARIA `[role]` that require children to contain a specific `[role]` have all required children.", + "description": "Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions. [Learn more about roles and required children elements](https://dequeuniversity.com/rules/axe/4.10/aria-required-children).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-required-parent": { + "id": "aria-required-parent", + "title": "`[role]`s are contained by their required parent element", + "description": "Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions. [Learn more about ARIA roles and required parent element](https://dequeuniversity.com/rules/axe/4.10/aria-required-parent).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-roles": { + "id": "aria-roles", + "title": "`[role]` values are valid", + "description": "ARIA roles must have valid values in order to perform their intended accessibility functions. [Learn more about valid ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-roles).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-text": { + "id": "aria-text", + "title": "Elements with the `role=text` attribute do not have focusable descendents.", + "description": "Adding `role=text` around a text node split by markup enables VoiceOver to treat it as one phrase, but the element's focusable descendents will not be announced. [Learn more about the `role=text` attribute](https://dequeuniversity.com/rules/axe/4.10/aria-text).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-toggle-field-name": { + "id": "aria-toggle-field-name", + "title": "ARIA toggle fields have accessible names", + "description": "When a toggle field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about toggle fields](https://dequeuniversity.com/rules/axe/4.10/aria-toggle-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-tooltip-name": { + "id": "aria-tooltip-name", + "title": "ARIA `tooltip` elements have accessible names", + "description": "When a tooltip element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `tooltip` elements](https://dequeuniversity.com/rules/axe/4.10/aria-tooltip-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-treeitem-name": { + "id": "aria-treeitem-name", + "title": "ARIA `treeitem` elements have accessible names", + "description": "When a `treeitem` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about labeling `treeitem` elements](https://dequeuniversity.com/rules/axe/4.10/aria-treeitem-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-valid-attr-value": { + "id": "aria-valid-attr-value", + "title": "`[aria-*]` attributes have valid values", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more about valid values for ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr-value).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-valid-attr": { + "id": "aria-valid-attr", + "title": "`[aria-*]` attributes are valid and not misspelled", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more about valid ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "button-name": { + "id": "button-name", + "title": "Buttons have an accessible name", + "description": "When a button doesn't have an accessible name, screen readers announce it as \"button\", making it unusable for users who rely on screen readers. [Learn how to make buttons more accessible](https://dequeuniversity.com/rules/axe/4.10/button-name).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "bypass": { + "id": "bypass", + "title": "The page contains a heading, skip link, or landmark region", + "description": "Adding ways to bypass repetitive content lets keyboard users navigate the page more efficiently. [Learn more about bypass blocks](https://dequeuniversity.com/rules/axe/4.10/bypass).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "color-contrast": { + "id": "color-contrast", + "title": "Background and foreground colors have a sufficient contrast ratio", + "description": "Low-contrast text is difficult or impossible for many users to read. [Learn how to provide sufficient color contrast](https://dequeuniversity.com/rules/axe/4.10/color-contrast).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "definition-list": { + "id": "definition-list", + "title": "`
    `'s contain only properly-ordered `
    ` and `
    ` groups, `", + "message": "Syntax not understood" + }, + { + "index": "9", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "11", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "12", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "15", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "16", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "17", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "18", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "19", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "23", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "24", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "25", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "29", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "30", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "31", + "line": " Interpop — Soft Power & Cultura Pop", + "message": "Syntax not understood" + }, + { + "index": "32", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "33", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "34", + "line": "
    ", + "message": "Syntax not understood" + }, + { + "index": "35", + "line": "
    ", + "message": "Syntax not understood" + }, + { + "index": "36", + "line": "
    ", + "message": "Syntax not understood" + }, + { + "index": "37", + "line": "
    ", + "message": "Syntax not understood" + }, + { + "index": "38", + "line": "
    ", + "message": "Syntax not understood" + }, + { + "index": "39", + "line": "
    ", + "message": "Syntax not understood" + }, + { + "index": "40", + "line": "
    ", + "message": "Syntax not understood" + }, + { + "index": "41", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "42", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "43", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "46", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "47", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "53", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "54", + "line": "", + "message": "Syntax not understood" + } + ] + } + }, + "hreflang": { + "id": "hreflang", + "title": "Document has a valid `hreflang`", + "description": "hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more about `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + }, + "label": "" + } + ], + "items": [] + } + }, + "canonical": { + "id": "canonical", + "title": "Document has a valid `rel=canonical`", + "description": "Canonical links suggest which URL to show in search results. [Learn more about canonical links](https://developer.chrome.com/docs/lighthouse/seo/canonical/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "structured-data": { + "id": "structured-data", + "title": "Structured data is valid", + "description": "Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more about Structured Data](https://developer.chrome.com/docs/lighthouse/seo/structured-data/).", + "score": null, + "scoreDisplayMode": "manual" + }, + "bf-cache": { + "id": "bf-cache", + "title": "Page prevented back/forward cache restoration", + "description": "Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://developer.chrome.com/docs/lighthouse/performance/bf-cache/)", + "score": 0, + "scoreDisplayMode": "binary", + "displayValue": "1 failure reason", + "details": { + "type": "table", + "headings": [ + { + "key": "reason", + "valueType": "text", + "subItemsHeading": { + "key": "frameUrl", + "valueType": "url" + }, + "label": "Failure reason" + }, + { + "key": "failureType", + "valueType": "text", + "label": "Failure type" + } + ], + "items": [ + { + "reason": "Pages with WebSocket cannot enter back/forward cache.", + "failureType": "Pending browser support", + "subItems": { + "type": "subitems", + "items": [ + { + "frameUrl": "http://localhost:5173/" + } + ] + }, + "protocolReason": "WebSocket" + } + ] + }, + "guidanceLevel": 4 + }, + "cache-insight": { + "id": "cache-insight", + "title": "Use efficient cache lifetimes", + "description": "A long cache lifetime can speed up repeat visits to your page. [Learn more](https://web.dev/uses-long-cache-ttl/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Request" + }, + { + "key": "cacheLifetimeMs", + "valueType": "ms", + "label": "Cache TTL", + "displayUnit": "duration" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size", + "displayUnit": "kb", + "granularity": 1 + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["uses-long-cache-ttl"] + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "text", + "value": "Total" + }, + "score": 0.176479 + }, + { + "node": { + "type": "node", + "lhId": "page-1-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV", + "selector": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual", + "boundingRect": { + "top": 494, + "bottom": 674, + "left": 24, + "right": 388, + "width": 364, + "height": 180 + }, + "snippet": "
    ", + "nodeLabel": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual" + }, + "score": 0.176479 + } + ] + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": [ + "layout-shifts", + "non-composited-animations", + "unsized-images" + ] + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": 0.5, + "scoreDisplayMode": "metricSavings", + "displayValue": "Est savings of 2 KiB", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "checklist", + "items": { + "noRedirects": { + "label": "Avoids redirects", + "value": true + }, + "serverResponseIsFast": { + "label": "Server responds quickly (observed 5 ms)", + "value": true + }, + "usesCompression": { + "label": "No compression applied", + "value": false + } + }, + "debugData": { + "type": "debugdata", + "redirectDuration": 0, + "serverResponseTime": 5, + "uncompressedResponseBytes": 1561, + "wastedBytes": 1561 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "redirects", + "server-response-time", + "uses-text-compression" + ] + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 105 + } + }, + { + "statistic": "Most children", + "node": { + "type": "node", + "lhId": "page-6-UL", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,0,DIV,1,NAV,0,DIV,1,UL", + "selector": "div.footer__top > nav.footer__links > div.footer__col > ul", + "boundingRect": { + "top": 1383, + "bottom": 1559, + "left": 24, + "right": 190, + "width": 166, + "height": 176 + }, + "snippet": "
      ", + "nodeLabel": "Música\nModa\nCinema\nLiteratura\nCultura Digital" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 5 + } + }, + { + "statistic": "DOM depth", + "node": { + "type": "node", + "lhId": "page-7-EM", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1,1,EM", + "selector": "div.home-hero__grid > div.home-hero__text > h1#hero-manifesto > em", + "boundingRect": { + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "snippet": "", + "nodeLabel": "Interpop" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 10 + } + } + ], + "debugData": { + "type": "debugdata", + "totalElements": 105, + "maxChildren": 5, + "maxDepth": 10 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["dom-size"] + }, + "duplicated-javascript-insight": { + "id": "duplicated-javascript-insight", + "title": "Duplicated JavaScript", + "description": "Remove large, duplicate JavaScript modules from bundles to reduce unnecessary bytes consumed by network activity.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "url", + "valueType": "url" + }, + "label": "Source" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "subItemsHeading": { + "key": "sourceTransferBytes" + }, + "granularity": 10, + "label": "Duplicated bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2, + "replacesAudits": ["duplicated-javascript"] + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["font-display"] + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "A forced reflow occurs when JavaScript queries geometric properties (such as offsetWidth) after styles have been invalidated by a change to the DOM state. This can result in poor performance. Learn more about [forced reflows](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and possible mitigations.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "reflowTime", + "valueType": "ms", + "granularity": 1, + "label": "Total reflow time" + } + ], + "items": [] + } + ] + }, + "guidanceLevel": 3 + }, + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + } + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Resource Size" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Est Savings", + "subItemsHeading": { + "key": "wastedBytes", + "valueType": "bytes" + } + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "modern-image-formats", + "uses-optimized-images", + "efficient-animated-content", + "uses-responsive-images" + ] + }, + "inp-breakdown-insight": { + "id": "inp-breakdown-insight", + "title": "INP breakdown", + "description": "Start investigating with the longest subpart. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["work-during-interaction"] + }, + "lcp-breakdown-insight": { + "id": "lcp-breakdown-insight", + "title": "LCP breakdown", + "description": "Each [subpart has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Subpart" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "subpart": "timeToFirstByte", + "label": "Time to first byte", + "duration": 11.616000003814698 + }, + { + "subpart": "elementRenderDelay", + "label": "Element render delay", + "duration": 630.7389999961853 + } + ] + }, + { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 136, + "bottom": 341, + "left": 24, + "right": 388, + "width": 364, + "height": 205 + }, + "snippet": "

      ", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["largest-contentful-paint-element"] + }, + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["prioritize-lcp-image", "lcp-lazy-loaded"] + }, + "legacy-javascript-insight": { + "id": "legacy-javascript-insight", + "title": "Legacy JavaScript", + "description": "Polyfills and transforms enable older browsers to use new JavaScript features. However, many aren't necessary for modern browsers. Consider modifying your JavaScript build process to not transpile [Baseline](https://web.dev/articles/baseline-and-polyfills) features, unless you know you must support older browsers. [Learn why most sites can deploy ES6+ code without transpiling](https://philipwalton.com/articles/the-state-of-es5-on-the-web/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "subItemsHeading": { + "key": "location", + "valueType": "source-location" + }, + "label": "URL" + }, + { + "key": null, + "valueType": "code", + "subItemsHeading": { + "key": "signal" + }, + "label": "" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Wasted bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2 + }, + "modern-http-insight": { + "id": "modern-http-insight", + "title": "Modern HTTP", + "description": "HTTP/2 and HTTP/3 offer many benefits over HTTP/1.1, such as multiplexing. [Learn more about using modern HTTP](https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "network-dependency-tree-insight": { + "id": "network-dependency-tree-insight", + "title": "Network dependency tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "list-section", + "value": { + "type": "network-tree", + "chains": { + "285C12AFD6B4C9406E9A40BA30F26945": { + "url": "http://localhost:5173/", + "navStartToEndTime": 32, + "transferSize": 2584, + "isLongest": true, + "children": { + "182882.3": { + "url": "http://localhost:5173/src/main.tsx", + "navStartToEndTime": 61, + "transferSize": 2330, + "isLongest": true, + "children": { + "182882.14": { + "url": "http://localhost:5173/src/contexts/AuthContext.tsx", + "navStartToEndTime": 93, + "transferSize": 11746, + "isLongest": true, + "children": { + "182882.19": { + "url": "http://localhost:5173/src/services/authService.ts", + "navStartToEndTime": 117, + "transferSize": 5810, + "isLongest": true, + "children": { + "182882.40": { + "url": "http://localhost:5173/src/services/api.ts", + "navStartToEndTime": 180, + "transferSize": 7075, + "isLongest": true, + "children": { + "182882.75": { + "url": "http://localhost:5173/node_modules/.vite/deps/axios.js?v=b37c83f7", + "navStartToEndTime": 258, + "transferSize": 108020, + "isLongest": true, + "children": { + "182882.128": { + "url": "http://localhost:8000/api/v1/auth/me/", + "navStartToEndTime": 680, + "transferSize": 0, + "isLongest": true, + "children": {} + }, + "182882.127": { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "navStartToEndTime": 679, + "transferSize": 0, + "children": {} + }, + "182882.126": { + "url": "http://localhost:8000/api/v1/auth/me/", + "navStartToEndTime": 679, + "transferSize": 0, + "children": {} + }, + "182882.125": { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "navStartToEndTime": 678, + "transferSize": 0, + "children": {} + } + } + } + } + } + } + } + } + }, + "182882.13": { + "url": "http://localhost:5173/src/App.tsx", + "navStartToEndTime": 93, + "transferSize": 2529, + "children": { + "182882.18": { + "url": "http://localhost:5173/src/router/AppRouter.tsx", + "navStartToEndTime": 117, + "transferSize": 21611, + "children": { + "182882.30": { + "url": "http://localhost:5173/src/pages/Article.tsx", + "navStartToEndTime": 166, + "transferSize": 35202, + "children": { + "182882.66": { + "url": "http://localhost:5173/src/components/article/ArticleComments.tsx", + "navStartToEndTime": 224, + "transferSize": 26599, + "children": { + "182882.88": { + "url": "http://localhost:5173/src/components/ui/CommentItem.tsx", + "navStartToEndTime": 272, + "transferSize": 24618, + "children": { + "182882.97": { + "url": "http://localhost:5173/src/components/ui/CommentItem.css", + "navStartToEndTime": 282, + "transferSize": 4263, + "children": {} + } + } + }, + "182882.89": { + "url": "http://localhost:5173/src/services/commentService.ts", + "navStartToEndTime": 272, + "transferSize": 2883, + "children": {} + } + } + }, + "182882.61": { + "url": "http://localhost:5173/src/components/ui/Avatar.tsx", + "navStartToEndTime": 221, + "transferSize": 4592, + "children": { + "182882.87": { + "url": "http://localhost:5173/src/components/ui/Avatar.css", + "navStartToEndTime": 269, + "transferSize": 1101, + "children": {} + } + } + }, + "182882.62": { + "url": "http://localhost:5173/src/components/ui/Badge.tsx", + "navStartToEndTime": 217, + "transferSize": 3273, + "children": { + "182882.86": { + "url": "http://localhost:5173/src/components/ui/Badge.css", + "navStartToEndTime": 268, + "transferSize": 2103, + "children": {} + } + } + }, + "182882.69": { + "url": "http://localhost:5173/src/pages/Article.css", + "navStartToEndTime": 236, + "transferSize": 13288, + "children": {} + }, + "182882.68": { + "url": "http://localhost:5173/src/styles/article-body.css", + "navStartToEndTime": 232, + "transferSize": 2477, + "children": {} + }, + "182882.67": { + "url": "http://localhost:5173/src/utils/renderArticleBody.tsx", + "navStartToEndTime": 229, + "transferSize": 6332, + "children": {} + }, + "182882.65": { + "url": "http://localhost:5173/src/utils/formatDate.ts", + "navStartToEndTime": 222, + "transferSize": 5113, + "children": {} + }, + "182882.63": { + "url": "http://localhost:5173/src/components/article/ArticleShareBar.tsx", + "navStartToEndTime": 220, + "transferSize": 15920, + "children": {} + }, + "182882.64": { + "url": "http://localhost:5173/src/components/article/ArticleAdminActions.tsx", + "navStartToEndTime": 219, + "transferSize": 11522, + "children": {} + } + } + }, + "182882.31": { + "url": "http://localhost:5173/src/pages/Auth/Login.tsx", + "navStartToEndTime": 166, + "transferSize": 13097, + "children": { + "182882.58": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.tsx", + "navStartToEndTime": 210, + "transferSize": 19518, + "children": { + "182882.84": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.tsx", + "navStartToEndTime": 265, + "transferSize": 5342, + "children": { + "182882.95": { + "url": "http://localhost:5173/src/assets/seek-white.svg?import", + "navStartToEndTime": 281, + "transferSize": 10333, + "children": {} + }, + "182882.96": { + "url": "http://localhost:5173/src/components/ui/DevelopedBy.css", + "navStartToEndTime": 280, + "transferSize": 1532, + "children": {} + } + } + }, + "182882.85": { + "url": "http://localhost:5173/src/components/layout/AuthLayout.css", + "navStartToEndTime": 270, + "transferSize": 4726, + "children": {} + } + } + }, + "182882.59": { + "url": "http://localhost:5173/src/components/ui/Input.tsx", + "navStartToEndTime": 209, + "transferSize": 4439, + "children": { + "182882.83": { + "url": "http://localhost:5173/src/components/ui/Input.css", + "navStartToEndTime": 263, + "transferSize": 1768, + "children": {} + } + } + }, + "182882.60": { + "url": "http://localhost:5173/src/pages/Auth/Auth.css", + "navStartToEndTime": 216, + "transferSize": 3256, + "children": {} + } + } + }, + "182882.24": { + "url": "http://localhost:5173/src/pages/Home.tsx", + "navStartToEndTime": 144, + "transferSize": 22299, + "children": { + "182882.43": { + "url": "http://localhost:5173/src/components/layout/PageLayout.tsx", + "navStartToEndTime": 190, + "transferSize": 4791, + "children": { + "182882.79": { + "url": "http://localhost:5173/src/components/layout/Footer.tsx", + "navStartToEndTime": 263, + "transferSize": 21715, + "children": { + "182882.94": { + "url": "http://localhost:5173/src/components/layout/Footer.css", + "navStartToEndTime": 280, + "transferSize": 5121, + "children": {} + } + } + }, + "182882.78": { + "url": "http://localhost:5173/src/components/layout/Navbar.tsx", + "navStartToEndTime": 255, + "transferSize": 21741, + "children": { + "182882.92": { + "url": "http://localhost:5173/src/components/layout/NavbarUserMenu.tsx", + "navStartToEndTime": 279, + "transferSize": 20091, + "children": {} + }, + "182882.93": { + "url": "http://localhost:5173/src/components/layout/Navbar.css", + "navStartToEndTime": 279, + "transferSize": 8587, + "children": {} + } + } + }, + "182882.80": { + "url": "http://localhost:5173/src/components/layout/PageLayout.css", + "navStartToEndTime": 259, + "transferSize": 1789, + "children": {} + } + } + }, + "182882.45": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.tsx", + "navStartToEndTime": 189, + "transferSize": 35878, + "children": { + "182882.77": { + "url": "http://localhost:5173/src/components/ui/NewsCarousel.css", + "navStartToEndTime": 256, + "transferSize": 3997, + "children": {} + } + } + }, + "182882.44": { + "url": "http://localhost:5173/src/components/ui/NewsCard.tsx", + "navStartToEndTime": 189, + "transferSize": 12632, + "children": { + "182882.76": { + "url": "http://localhost:5173/src/components/ui/NewsCard.css", + "navStartToEndTime": 250, + "transferSize": 6902, + "children": {} + } + } + }, + "182882.48": { + "url": "http://localhost:5173/src/pages/Home.css", + "navStartToEndTime": 198, + "transferSize": 9483, + "children": {} + }, + "182882.47": { + "url": "http://localhost:5173/src/assets/interpop-logo.svg?import", + "navStartToEndTime": 196, + "transferSize": 689, + "children": {} + }, + "182882.46": { + "url": "http://localhost:5173/src/services/articleService.ts", + "navStartToEndTime": 190, + "transferSize": 9838, + "children": {} + } + } + }, + "182882.32": { + "url": "http://localhost:5173/src/pages/Auth/Register.tsx", + "navStartToEndTime": 167, + "transferSize": 27959, + "children": { + "182882.70": { + "url": "http://localhost:5173/src/components/ui/Modal.tsx", + "navStartToEndTime": 238, + "transferSize": 13726, + "children": { + "182882.91": { + "url": "http://localhost:5173/src/components/ui/Modal.css", + "navStartToEndTime": 277, + "transferSize": 2244, + "children": {} + } + } + }, + "182882.71": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.tsx", + "navStartToEndTime": 234, + "transferSize": 6890, + "children": { + "182882.90": { + "url": "http://localhost:5173/src/components/ui/PasswordChecklist.css", + "navStartToEndTime": 273, + "transferSize": 1998, + "children": {} + } + } + }, + "182882.72": { + "url": "http://localhost:5173/src/utils/passwordRules.ts", + "navStartToEndTime": 236, + "transferSize": 3410, + "children": {} + } + } + }, + "182882.28": { + "url": "http://localhost:5173/src/pages/Legal/Termos.tsx", + "navStartToEndTime": 157, + "transferSize": 4765, + "children": { + "182882.55": { + "url": "http://localhost:5173/src/pages/Legal/LegalContent.tsx", + "navStartToEndTime": 206, + "transferSize": 75711, + "children": { + "182882.82": { + "url": "http://localhost:5173/src/pages/Legal/Legal.css", + "navStartToEndTime": 264, + "transferSize": 4112, + "children": {} + } + } + } + } + }, + "182882.26": { + "url": "http://localhost:5173/src/pages/Newsletter.tsx", + "navStartToEndTime": 147, + "transferSize": 19118, + "children": { + "182882.49": { + "url": "http://localhost:5173/src/components/ui/Button.tsx", + "navStartToEndTime": 197, + "transferSize": 3750, + "children": { + "182882.81": { + "url": "http://localhost:5173/src/components/ui/Button.css", + "navStartToEndTime": 262, + "transferSize": 2160, + "children": {} + } + } + }, + "182882.52": { + "url": "http://localhost:5173/src/pages/Newsletter.css", + "navStartToEndTime": 200, + "transferSize": 4998, + "children": {} + }, + "182882.51": { + "url": "http://localhost:5173/src/utils/extractApiError.ts", + "navStartToEndTime": 199, + "transferSize": 7110, + "children": {} + }, + "182882.50": { + "url": "http://localhost:5173/src/services/newsletterService.ts", + "navStartToEndTime": 197, + "transferSize": 1363, + "children": {} + } + } + }, + "182882.35": { + "url": "http://localhost:5173/src/pages/Perfil.tsx", + "navStartToEndTime": 176, + "transferSize": 55832, + "children": { + "182882.73": { + "url": "http://localhost:5173/src/pages/Perfil.css", + "navStartToEndTime": 242, + "transferSize": 4993, + "children": {} + } + } + }, + "182882.36": { + "url": "http://localhost:5173/src/pages/NotFound.tsx", + "navStartToEndTime": 179, + "transferSize": 10579, + "children": { + "182882.74": { + "url": "http://localhost:5173/src/pages/NotFound.css", + "navStartToEndTime": 241, + "transferSize": 2228, + "children": {} + } + } + }, + "182882.27": { + "url": "http://localhost:5173/src/pages/About/index.tsx", + "navStartToEndTime": 158, + "transferSize": 5790, + "children": { + "182882.56": { + "url": "http://localhost:5173/src/pages/About/AboutContent.tsx", + "navStartToEndTime": 208, + "transferSize": 15734, + "children": {} + }, + "182882.57": { + "url": "http://localhost:5173/src/pages/About/About.css", + "navStartToEndTime": 208, + "transferSize": 3917, + "children": {} + } + } + }, + "182882.25": { + "url": "http://localhost:5173/src/pages/News.tsx", + "navStartToEndTime": 147, + "transferSize": 31303, + "children": { + "182882.54": { + "url": "http://localhost:5173/src/pages/News.css", + "navStartToEndTime": 204, + "transferSize": 1882, + "children": {} + }, + "182882.53": { + "url": "http://localhost:5173/src/utils/categoryVariant.ts", + "navStartToEndTime": 201, + "transferSize": 2562, + "children": {} + } + } + }, + "182882.23": { + "url": "http://localhost:5173/src/components/ErrorFallback.tsx", + "navStartToEndTime": 143, + "transferSize": 9815, + "children": { + "182882.42": { + "url": "http://localhost:5173/src/components/ErrorFallback.css", + "navStartToEndTime": 187, + "transferSize": 3123, + "children": {} + } + } + }, + "182882.39": { + "url": "http://localhost:5173/src/router/ScrollToHashOrTop.tsx", + "navStartToEndTime": 181, + "transferSize": 10378, + "children": {} + }, + "182882.38": { + "url": "http://localhost:5173/src/router/AdminRoute.tsx", + "navStartToEndTime": 181, + "transferSize": 6218, + "children": {} + }, + "182882.37": { + "url": "http://localhost:5173/src/pages/Unsubscribe.tsx", + "navStartToEndTime": 180, + "transferSize": 12734, + "children": {} + }, + "182882.34": { + "url": "http://localhost:5173/src/pages/Auth/ResetPassword.tsx", + "navStartToEndTime": 172, + "transferSize": 18222, + "children": {} + }, + "182882.33": { + "url": "http://localhost:5173/src/pages/Auth/ForgotPassword.tsx", + "navStartToEndTime": 169, + "transferSize": 12833, + "children": {} + }, + "182882.29": { + "url": "http://localhost:5173/src/pages/Legal/Privacidade.tsx", + "navStartToEndTime": 165, + "transferSize": 4862, + "children": {} + }, + "182882.21": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=314775e0", + "navStartToEndTime": 160, + "transferSize": 399267, + "children": {} + }, + "182882.22": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-error-boundary.js?v=6d971406", + "navStartToEndTime": 141, + "transferSize": 3458, + "children": {} + } + } + } + } + }, + "182882.11": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa1be4d7", + "navStartToEndTime": 121, + "transferSize": 821294, + "children": { + "182882.41": { + "url": "http://localhost:5173/node_modules/.vite/deps/react-dom.js?v=f24ae126", + "navStartToEndTime": 188, + "transferSize": 14940, + "children": {} + } + } + }, + "182882.15": { + "url": "http://localhost:5173/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=f24ae126", + "navStartToEndTime": 77, + "transferSize": 10883, + "children": { + "182882.17": { + "url": "http://localhost:5173/node_modules/.vite/deps/chunk-B-1-B7_t.js?v=696798f9", + "navStartToEndTime": 96, + "transferSize": 7922, + "children": {} + } + } + }, + "182882.12": { + "url": "http://localhost:5173/src/styles/global.css", + "navStartToEndTime": 95, + "transferSize": 16873, + "children": {} + }, + "182882.10": { + "url": "http://localhost:5173/node_modules/.vite/deps/react.js?v=f24ae126", + "navStartToEndTime": 94, + "transferSize": 38613, + "children": {} + } + } + }, + "182882.2": { + "url": "http://localhost:5173/@vite/client", + "navStartToEndTime": 63, + "transferSize": 210114, + "children": { + "182882.16": { + "url": "http://localhost:5173/node_modules/vite/dist/client/env.mjs", + "navStartToEndTime": 97, + "transferSize": 3768, + "children": {} + } + } + }, + "182882.9": { + "url": "http://localhost:5173/site.webmanifest", + "navStartToEndTime": 92, + "transferSize": 796, + "children": {} + }, + "182882.8": { + "url": "http://localhost:5173/@react-refresh", + "navStartToEndTime": 70, + "transferSize": 112187, + "children": {} + } + } + } + }, + "longestChain": { + "duration": 680 + } + } + }, + { + "type": "list-section", + "title": "Preconnected origins", + "description": "[preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints help the browser establish a connection earlier in the page load, saving time when the first request for that origin is made. The following are the origins that the page preconnected to.", + "value": { + "type": "text", + "value": "no origins were preconnected" + } + }, + { + "type": "list-section", + "title": "Preconnect candidates", + "description": "Add [preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints to your most important origins, but try to use no more than 4.", + "value": { + "type": "text", + "value": "No additional origins are good candidates for preconnecting" + } + } + ] + }, + "guidanceLevel": 1, + "replacesAudits": ["critical-request-chains", "uses-rel-preconnect"] + }, + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources) can move these network requests out of the critical path.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Duration" + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["render-blocking-resources"] + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "3rd parties", + "description": "3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript/) to prioritize your page's content.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "3rd party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "mainThreadTime", + "granularity": 1, + "valueType": "ms", + "label": "Main thread time", + "subItemsHeading": { + "key": "mainThreadTime" + } + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["third-party-summary"] + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-5-META", + "path": "1,HTML,0,HEAD,7,META", + "selector": "head > meta", + "boundingRect": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "snippet": "", + "nodeLabel": "head > meta" + } + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["viewport"] + } + }, + "configSettings": { + "output": ["json"], + "maxWaitForFcp": 30000, + "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "formFactor": "mobile", + "throttling": { + "rttMs": 150, + "throughputKbps": 1638.4, + "requestLatencyMs": 562.5, + "downloadThroughputKbps": 1474.5600000000002, + "uploadThroughputKbps": 675, + "cpuSlowdownMultiplier": 4 + }, + "throttlingMethod": "simulate", + "screenEmulation": { + "mobile": true, + "width": 412, + "height": 823, + "deviceScaleFactor": 1.75, + "disabled": false + }, + "emulatedUserAgent": "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36", + "auditMode": false, + "gatherMode": false, + "clearStorageTypes": [ + "file_systems", + "shader_cache", + "service_workers", + "cache_storage" + ], + "disableStorageReset": false, + "debugNavigation": false, + "channel": "cli", + "usePassiveGathering": false, + "disableFullPageScreenshot": false, + "skipAboutBlank": false, + "blankPage": "about:blank", + "ignoreStatusCode": false, + "locale": "en-US", + "blockedUrlPatterns": null, + "additionalTraceCategories": null, + "extraHeaders": null, + "precomputedLanternData": null, + "onlyAudits": null, + "onlyCategories": null, + "skipAudits": null + }, + "categories": { + "performance": { + "title": "Performance", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "first-contentful-paint", + "weight": 10, + "group": "metrics", + "acronym": "FCP" + }, + { + "id": "largest-contentful-paint", + "weight": 25, + "group": "metrics", + "acronym": "LCP" + }, + { + "id": "total-blocking-time", + "weight": 30, + "group": "metrics", + "acronym": "TBT" + }, + { + "id": "cumulative-layout-shift", + "weight": 25, + "group": "metrics", + "acronym": "CLS" + }, + { + "id": "speed-index", + "weight": 10, + "group": "metrics", + "acronym": "SI" + }, + { + "id": "cache-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "duplicated-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "inp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "legacy-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "modern-http-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-dependency-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "third-parties-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interactive", + "weight": 0, + "group": "hidden", + "acronym": "TTI" + }, + { + "id": "max-potential-fid", + "weight": 0, + "group": "hidden" + }, + { + "id": "first-meaningful-paint", + "weight": 0, + "acronym": "FMP", + "group": "hidden" + }, + { + "id": "render-blocking-resources", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-responsive-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "offscreen-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-css", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-css-rules", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-optimized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "modern-image-formats", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-text-compression", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-rel-preconnect", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "server-response-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "redirects", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-http2", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "efficient-animated-content", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "duplicated-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "legacy-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "prioritize-lcp-image", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "total-byte-weight", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-long-cache-ttl", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "dom-size", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "critical-request-chains", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "user-timings", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bootup-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "mainthread-work-breakdown", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "font-display", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-summary", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-facades", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "largest-contentful-paint-element", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "lcp-lazy-loaded", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "layout-shifts", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-passive-event-listeners", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "no-document-write", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "long-tasks", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "non-composited-animations", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unsized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "viewport", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bf-cache", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "network-requests", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-rtt", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-server-latency", + "weight": 0, + "group": "hidden" + }, + { + "id": "main-thread-tasks", + "weight": 0, + "group": "hidden" + }, + { + "id": "diagnostics", + "weight": 0, + "group": "hidden" + }, + { + "id": "metrics", + "weight": 0, + "group": "hidden" + }, + { + "id": "screenshot-thumbnails", + "weight": 0, + "group": "hidden" + }, + { + "id": "final-screenshot", + "weight": 0, + "group": "hidden" + }, + { + "id": "script-treemap-data", + "weight": 0, + "group": "hidden" + }, + { + "id": "resource-summary", + "weight": 0, + "group": "hidden" + } + ], + "id": "performance", + "score": 0.43 + }, + "accessibility": { + "title": "Accessibility", + "description": "These checks highlight opportunities to [improve the accessibility of your web app](https://developer.chrome.com/docs/lighthouse/accessibility/). Automatic detection can only detect a subset of issues and does not guarantee the accessibility of your web app, so [manual testing](https://web.dev/articles/how-to-review) is also encouraged.", + "manualDescription": "These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://web.dev/articles/how-to-review).", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "accesskeys", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "aria-allowed-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-allowed-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-command-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-conditional-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-deprecated-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-dialog-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-body", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-focus", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-input-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-meter-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-progressbar-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-prohibited-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-required-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-required-children", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-required-parent", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-roles", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-text", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-toggle-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-tooltip-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-treeitem-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr-value", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "button-name", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "bypass", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "color-contrast", + "weight": 7, + "group": "a11y-color-contrast" + }, + { + "id": "definition-list", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "dlitem", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "document-title", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "duplicate-id-aria", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "form-field-multiple-labels", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "frame-title", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "heading-order", + "weight": 3, + "group": "a11y-navigation" + }, + { + "id": "html-has-lang", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-lang-valid", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-xml-lang-mismatch", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "image-alt", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "image-redundant-alt", + "weight": 1, + "group": "a11y-names-labels" + }, + { + "id": "input-button-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "input-image-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "label", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "link-in-text-block", + "weight": 0, + "group": "a11y-color-contrast" + }, + { + "id": "link-name", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "list", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "listitem", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "meta-refresh", + "weight": 0, + "group": "a11y-best-practices" + }, + { + "id": "meta-viewport", + "weight": 10, + "group": "a11y-best-practices" + }, + { + "id": "object-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "select-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "skip-link", + "weight": 3, + "group": "a11y-names-labels" + }, + { + "id": "tabindex", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "table-duplicate-name", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "target-size", + "weight": 7, + "group": "a11y-best-practices" + }, + { + "id": "td-headers-attr", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "th-has-data-cells", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "valid-lang", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "video-caption", + "weight": 0, + "group": "a11y-audio-video" + }, + { + "id": "focusable-controls", + "weight": 0 + }, + { + "id": "interactive-element-affordance", + "weight": 0 + }, + { + "id": "logical-tab-order", + "weight": 0 + }, + { + "id": "visual-order-follows-dom", + "weight": 0 + }, + { + "id": "focus-traps", + "weight": 0 + }, + { + "id": "managed-focus", + "weight": 0 + }, + { + "id": "use-landmarks", + "weight": 0 + }, + { + "id": "offscreen-content-hidden", + "weight": 0 + }, + { + "id": "custom-controls-labels", + "weight": 0 + }, + { + "id": "custom-controls-roles", + "weight": 0 + }, + { + "id": "empty-heading", + "weight": 0, + "group": "hidden" + }, + { + "id": "identical-links-same-purpose", + "weight": 0, + "group": "hidden" + }, + { + "id": "landmark-one-main", + "weight": 0, + "group": "hidden" + }, + { + "id": "label-content-name-mismatch", + "weight": 0, + "group": "hidden" + }, + { + "id": "table-fake-caption", + "weight": 0, + "group": "hidden" + }, + { + "id": "td-has-header", + "weight": 0, + "group": "hidden" + } + ], + "id": "accessibility", + "score": 1 + }, + "best-practices": { + "title": "Best Practices", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "is-on-https", + "weight": 5, + "group": "best-practices-trust-safety" + }, + { + "id": "redirects-http", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "geolocation-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "notification-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "csp-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "has-hsts", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "origin-isolation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "clickjacking-mitigation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "trusted-types-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "paste-preventing-inputs", + "weight": 3, + "group": "best-practices-ux" + }, + { + "id": "image-aspect-ratio", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "image-size-responsive", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "viewport", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "font-size", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "doctype", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "charset", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "js-libraries", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "deprecations", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "third-party-cookies", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "errors-in-console", + "weight": 1, + "group": "best-practices-general" + }, + { + "id": "valid-source-maps", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "inspector-issues", + "weight": 1, + "group": "best-practices-general" + } + ], + "id": "best-practices", + "score": 0.96 + }, + "seo": { + "title": "SEO", + "description": "These checks ensure that your page is following basic search engine optimization advice. There are many additional factors Lighthouse does not score here that may affect your search ranking, including performance on [Core Web Vitals](https://web.dev/explore/vitals). [Learn more about Google Search Essentials](https://support.google.com/webmasters/answer/35769).", + "manualDescription": "Run these additional validators on your site to check additional SEO best practices.", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "is-crawlable", + "weight": 4.043478260869565, + "group": "seo-crawl" + }, + { + "id": "document-title", + "weight": 1, + "group": "seo-content" + }, + { + "id": "meta-description", + "weight": 1, + "group": "seo-content" + }, + { + "id": "http-status-code", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "link-text", + "weight": 1, + "group": "seo-content" + }, + { + "id": "crawlable-anchors", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "robots-txt", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "image-alt", + "weight": 1, + "group": "seo-content" + }, + { + "id": "hreflang", + "weight": 1, + "group": "seo-content" + }, + { + "id": "canonical", + "weight": 0, + "group": "seo-content" + }, + { + "id": "structured-data", + "weight": 0 + } + ], + "id": "seo", + "score": 0.92 + } + }, + "categoryGroups": { + "metrics": { + "title": "Metrics" + }, + "insights": { + "title": "Insights", + "description": "These insights are also available in the Chrome DevTools Performance Panel - [record a trace](https://developer.chrome.com/docs/devtools/performance/reference) to view more detailed information." + }, + "diagnostics": { + "title": "Diagnostics", + "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." + }, + "a11y-best-practices": { + "title": "Best practices", + "description": "These items highlight common accessibility best practices." + }, + "a11y-color-contrast": { + "title": "Contrast", + "description": "These are opportunities to improve the legibility of your content." + }, + "a11y-names-labels": { + "title": "Names and labels", + "description": "These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-navigation": { + "title": "Navigation", + "description": "These are opportunities to improve keyboard navigation in your application." + }, + "a11y-aria": { + "title": "ARIA", + "description": "These are opportunities to improve the usage of ARIA in your application which may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-language": { + "title": "Internationalization and localization", + "description": "These are opportunities to improve the interpretation of your content by users in different locales." + }, + "a11y-audio-video": { + "title": "Audio and video", + "description": "These are opportunities to provide alternative content for audio and video. This may improve the experience for users with hearing or vision impairments." + }, + "a11y-tables-lists": { + "title": "Tables and lists", + "description": "These are opportunities to improve the experience of reading tabular or list data using assistive technology, like a screen reader." + }, + "seo-mobile": { + "title": "Mobile Friendly", + "description": "Make sure your pages are mobile friendly so users don’t have to pinch or zoom in order to read the content pages. [Learn how to make pages mobile-friendly](https://developers.google.com/search/mobile-sites/)." + }, + "seo-content": { + "title": "Content Best Practices", + "description": "Format your HTML in a way that enables crawlers to better understand your app’s content." + }, + "seo-crawl": { + "title": "Crawling and Indexing", + "description": "To appear in search results, crawlers need access to your app." + }, + "best-practices-trust-safety": { + "title": "Trust and Safety" + }, + "best-practices-ux": { + "title": "User Experience" + }, + "best-practices-browser-compat": { + "title": "Browser Compatibility" + }, + "best-practices-general": { + "title": "General" + }, + "hidden": { + "title": "" + } + }, + "stackPacks": [], + "entities": [ + { + "name": "localhost", + "origins": ["http://localhost:5173", "http://localhost:8000"], + "isFirstParty": true, + "isUnrecognized": true + }, + { + "name": "vlibras.gov.br", + "origins": ["https://vlibras.gov.br"], + "isUnrecognized": true + } + ], + "fullPageScreenshot": { + "screenshot": { + "data": "data:image/webp;base64,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", + "width": 780, + "height": 1951 + }, + "nodes": { + "page-0-H1": { + "id": "hero-manifesto", + "top": 136, + "bottom": 239, + "left": 24, + "right": 595, + "width": 571, + "height": 102 + }, + "page-1-DIV": { + "id": "", + "top": 364, + "bottom": 544, + "left": 24, + "right": 756, + "width": 732, + "height": 180 + }, + "page-2-SECTION": { + "id": "", + "top": 569, + "bottom": 941, + "left": 0, + "right": 780, + "width": 780, + "height": 372 + }, + "page-3-P": { + "id": "", + "top": 259, + "bottom": 340, + "left": 24, + "right": 549, + "width": 525, + "height": 82 + }, + "page-4-EM": { + "id": "", + "top": 173, + "bottom": 201, + "left": 287, + "right": 404, + "width": 117, + "height": 28 + }, + "page-5-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "page-6-UL": { + "id": "", + "top": 1638, + "bottom": 1814, + "left": 24, + "right": 247, + "width": 223, + "height": 176 + }, + "page-7-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "page-8-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 200, + "right": 580, + "width": 380, + "height": 111 + }, + "page-9-IMG": { + "id": "", + "top": 1887, + "bottom": 1927, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "page-10-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-0-A": { + "id": "", + "top": -81, + "bottom": -34, + "left": 12, + "right": 194, + "width": 182, + "height": 46 + }, + "1-1-A": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-2-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-3-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-4-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-5-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-6-A": { + "id": "", + "top": 586, + "bottom": 609, + "left": 666, + "right": 756, + "width": 90, + "height": 22 + }, + "1-7-A": { + "id": "", + "top": 829, + "bottom": 877, + "left": 276, + "right": 504, + "width": 229, + "height": 48 + }, + "1-8-A": { + "id": "", + "top": 1642, + "bottom": 1659, + "left": 24, + "right": 72, + "width": 48, + "height": 17 + }, + "1-9-A": { + "id": "", + "top": 1680, + "bottom": 1697, + "left": 24, + "right": 61, + "width": 37, + "height": 17 + }, + "1-10-A": { + "id": "", + "top": 1717, + "bottom": 1734, + "left": 24, + "right": 74, + "width": 50, + "height": 17 + }, + "1-11-A": { + "id": "", + "top": 1755, + "bottom": 1772, + "left": 24, + "right": 87, + "width": 63, + "height": 17 + }, + "1-12-A": { + "id": "", + "top": 1792, + "bottom": 1809, + "left": 24, + "right": 117, + "width": 93, + "height": 17 + }, + "1-13-A": { + "id": "", + "top": 1642, + "bottom": 1659, + "left": 279, + "right": 346, + "width": 67, + "height": 17 + }, + "1-14-A": { + "id": "", + "top": 1680, + "bottom": 1697, + "left": 279, + "right": 336, + "width": 58, + "height": 17 + }, + "1-15-A": { + "id": "", + "top": 1717, + "bottom": 1734, + "left": 279, + "right": 333, + "width": 54, + "height": 17 + }, + "1-16-A": { + "id": "", + "top": 1755, + "bottom": 1772, + "left": 279, + "right": 331, + "width": 52, + "height": 17 + }, + "1-17-A": { + "id": "", + "top": 1642, + "bottom": 1659, + "left": 533, + "right": 573, + "width": 40, + "height": 17 + }, + "1-18-A": { + "id": "", + "top": 1680, + "bottom": 1697, + "left": 533, + "right": 607, + "width": 73, + "height": 17 + }, + "1-19-A": { + "id": "", + "top": 1717, + "bottom": 1734, + "left": 533, + "right": 646, + "width": 112, + "height": 17 + }, + "1-20-A": { + "id": "", + "top": 1887, + "bottom": 1927, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-21-A": { + "id": "", + "top": 1897, + "bottom": 1917, + "left": 393, + "right": 460, + "width": 67, + "height": 19 + }, + "1-22-A": { + "id": "", + "top": 1897, + "bottom": 1917, + "left": 480, + "right": 564, + "width": 84, + "height": 19 + }, + "1-23-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "1-24-BODY": { + "id": "", + "top": 0, + "bottom": 1951, + "left": 0, + "right": 780, + "width": 780, + "height": 1951 + }, + "1-25-FORM": { + "id": "", + "top": 1528, + "bottom": 1571, + "left": 24, + "right": 756, + "width": 732, + "height": 43 + }, + "1-26-INPUT": { + "id": "", + "top": 1528, + "bottom": 1571, + "left": 24, + "right": 641, + "width": 617, + "height": 43 + }, + "1-27-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-28-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 200, + "right": 580, + "width": 380, + "height": 111 + }, + "1-29-IMG": { + "id": "", + "top": 1887, + "bottom": 1927, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-30-IMG": { + "id": "", + "top": 1898, + "bottom": 1916, + "left": 716, + "right": 756, + "width": 40, + "height": 18 + }, + "1-31-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-32-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-33-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-34-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-35-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-36-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-37-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-38-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-39-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-40-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-41-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + } + } + }, + "timing": { + "entries": [ + { + "startTime": 1905.51, + "name": "lh:config", + "duration": 1077.15, + "entryType": "measure" + }, + { + "startTime": 1907.99, + "name": "lh:config:resolveArtifactsToDefns", + "duration": 63.06, + "entryType": "measure" + }, + { + "startTime": 2982.88, + "name": "lh:runner:gather", + "duration": 10224.78, + "entryType": "measure" + }, + { + "startTime": 3089.04, + "name": "lh:driver:connect", + "duration": 10.18, + "entryType": "measure" + }, + { + "startTime": 3099.75, + "name": "lh:driver:navigate", + "duration": 9.2, + "entryType": "measure" + }, + { + "startTime": 3109.49, + "name": "lh:gather:getBenchmarkIndex", + "duration": 1007.54, + "entryType": "measure" + }, + { + "startTime": 4117.26, + "name": "lh:gather:getVersion", + "duration": 1.14, + "entryType": "measure" + }, + { + "startTime": 4118.57, + "name": "lh:gather:getDevicePixelRatio", + "duration": 1.48, + "entryType": "measure" + }, + { + "startTime": 4120.54, + "name": "lh:prepare:navigationMode", + "duration": 47.72, + "entryType": "measure" + }, + { + "startTime": 4148.05, + "name": "lh:storage:clearDataForOrigin", + "duration": 7.63, + "entryType": "measure" + }, + { + "startTime": 4155.87, + "name": "lh:storage:clearBrowserCaches", + "duration": 6.14, + "entryType": "measure" + }, + { + "startTime": 4165.02, + "name": "lh:gather:prepareThrottlingAndNetwork", + "duration": 3.12, + "entryType": "measure" + }, + { + "startTime": 4213.48, + "name": "lh:driver:navigate", + "duration": 3056.76, + "entryType": "measure" + }, + { + "startTime": 8665.71, + "name": "lh:computed:NetworkRecords", + "duration": 5.51, + "entryType": "measure" + }, + { + "startTime": 8672.83, + "name": "lh:gather:getArtifact:DevtoolsLog", + "duration": 0.07, + "entryType": "measure" + }, + { + "startTime": 8672.93, + "name": "lh:gather:getArtifact:Trace", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 8672.98, + "name": "lh:gather:getArtifact:Accessibility", + "duration": 622.94, + "entryType": "measure" + }, + { + "startTime": 9295.96, + "name": "lh:gather:getArtifact:AnchorElements", + "duration": 514.63, + "entryType": "measure" + }, + { + "startTime": 9810.64, + "name": "lh:gather:getArtifact:ConsoleMessages", + "duration": 0.09, + "entryType": "measure" + }, + { + "startTime": 9810.75, + "name": "lh:gather:getArtifact:CSSUsage", + "duration": 72.41, + "entryType": "measure" + }, + { + "startTime": 9883.2, + "name": "lh:gather:getArtifact:Doctype", + "duration": 4.34, + "entryType": "measure" + }, + { + "startTime": 9887.57, + "name": "lh:gather:getArtifact:DOMStats", + "duration": 14.78, + "entryType": "measure" + }, + { + "startTime": 9902.4, + "name": "lh:gather:getArtifact:FontSize", + "duration": 55.81, + "entryType": "measure" + }, + { + "startTime": 9958.24, + "name": "lh:gather:getArtifact:Inputs", + "duration": 13.3, + "entryType": "measure" + }, + { + "startTime": 9971.57, + "name": "lh:gather:getArtifact:ImageElements", + "duration": 154.68, + "entryType": "measure" + }, + { + "startTime": 10126.54, + "name": "lh:gather:getArtifact:InspectorIssues", + "duration": 0.39, + "entryType": "measure" + }, + { + "startTime": 10126.98, + "name": "lh:gather:getArtifact:JsUsage", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 10127.21, + "name": "lh:gather:getArtifact:LinkElements", + "duration": 6.98, + "entryType": "measure" + }, + { + "startTime": 10133.84, + "name": "lh:computed:MainResource", + "duration": 0.3, + "entryType": "measure" + }, + { + "startTime": 10134.4, + "name": "lh:gather:getArtifact:MainDocumentContent", + "duration": 13.67, + "entryType": "measure" + }, + { + "startTime": 10148.13, + "name": "lh:gather:getArtifact:MetaElements", + "duration": 10.59, + "entryType": "measure" + }, + { + "startTime": 10158.79, + "name": "lh:gather:getArtifact:NetworkUserAgent", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 10159, + "name": "lh:gather:getArtifact:OptimizedImages", + "duration": 0.61, + "entryType": "measure" + }, + { + "startTime": 10159.67, + "name": "lh:gather:getArtifact:ResponseCompression", + "duration": 316.31, + "entryType": "measure" + }, + { + "startTime": 10476.04, + "name": "lh:gather:getArtifact:RobotsTxt", + "duration": 17.8, + "entryType": "measure" + }, + { + "startTime": 10493.88, + "name": "lh:gather:getArtifact:Scripts", + "duration": 0.41, + "entryType": "measure" + }, + { + "startTime": 10494.34, + "name": "lh:gather:getArtifact:SourceMaps", + "duration": 233.23, + "entryType": "measure" + }, + { + "startTime": 10727.61, + "name": "lh:gather:getArtifact:Stacks", + "duration": 39.64, + "entryType": "measure" + }, + { + "startTime": 10727.81, + "name": "lh:gather:collectStacks", + "duration": 39.41, + "entryType": "measure" + }, + { + "startTime": 10767.28, + "name": "lh:gather:getArtifact:Stylesheets", + "duration": 60.01, + "entryType": "measure" + }, + { + "startTime": 10827.49, + "name": "lh:gather:getArtifact:TraceElements", + "duration": 714.65, + "entryType": "measure" + }, + { + "startTime": 10827.76, + "name": "lh:computed:TraceEngineResult", + "duration": 647.5, + "entryType": "measure" + }, + { + "startTime": 10827.89, + "name": "lh:computed:ProcessedTrace", + "duration": 54.17, + "entryType": "measure" + }, + { + "startTime": 10884.59, + "name": "lh:computed:TraceEngineResult:total", + "duration": 561.51, + "entryType": "measure" + }, + { + "startTime": 10884.64, + "name": "lh:computed:TraceEngineResult:parse", + "duration": 375.4, + "entryType": "measure" + }, + { + "startTime": 10886.05, + "name": "lh:computed:TraceEngineResult:parse:handleEvent", + "duration": 249.62, + "entryType": "measure" + }, + { + "startTime": 11135.76, + "name": "lh:computed:TraceEngineResult:parse:Meta:finalize", + "duration": 1.85, + "entryType": "measure" + }, + { + "startTime": 11137.87, + "name": "lh:computed:TraceEngineResult:parse:AnimationFrames:finalize", + "duration": 1.65, + "entryType": "measure" + }, + { + "startTime": 11139.72, + "name": "lh:computed:TraceEngineResult:parse:Animations:finalize", + "duration": 2.66, + "entryType": "measure" + }, + { + "startTime": 11142.47, + "name": "lh:computed:TraceEngineResult:parse:Samples:finalize", + "duration": 1.46, + "entryType": "measure" + }, + { + "startTime": 11144, + "name": "lh:computed:TraceEngineResult:parse:AuctionWorklets:finalize", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 11145.54, + "name": "lh:computed:TraceEngineResult:parse:NetworkRequests:finalize", + "duration": 10.09, + "entryType": "measure" + }, + { + "startTime": 11155.69, + "name": "lh:computed:TraceEngineResult:parse:Renderer:finalize", + "duration": 27.38, + "entryType": "measure" + }, + { + "startTime": 11183.13, + "name": "lh:computed:TraceEngineResult:parse:Flows:finalize", + "duration": 11.11, + "entryType": "measure" + }, + { + "startTime": 11194.31, + "name": "lh:computed:TraceEngineResult:parse:AsyncJSCalls:finalize", + "duration": 3.35, + "entryType": "measure" + }, + { + "startTime": 11197.85, + "name": "lh:computed:TraceEngineResult:parse:DOMStats:finalize", + "duration": 1.25, + "entryType": "measure" + }, + { + "startTime": 11199.18, + "name": "lh:computed:TraceEngineResult:parse:UserTimings:finalize", + "duration": 3.08, + "entryType": "measure" + }, + { + "startTime": 11202.33, + "name": "lh:computed:TraceEngineResult:parse:ExtensionTraceData:finalize", + "duration": 4.99, + "entryType": "measure" + }, + { + "startTime": 11207.36, + "name": "lh:computed:TraceEngineResult:parse:LayerTree:finalize", + "duration": 1.78, + "entryType": "measure" + }, + { + "startTime": 11209.18, + "name": "lh:computed:TraceEngineResult:parse:Frames:finalize", + "duration": 17.01, + "entryType": "measure" + }, + { + "startTime": 11226.24, + "name": "lh:computed:TraceEngineResult:parse:GPU:finalize", + "duration": 1.31, + "entryType": "measure" + }, + { + "startTime": 11227.58, + "name": "lh:computed:TraceEngineResult:parse:ImagePainting:finalize", + "duration": 1.29, + "entryType": "measure" + }, + { + "startTime": 11228.95, + "name": "lh:computed:TraceEngineResult:parse:Initiators:finalize", + "duration": 2.18, + "entryType": "measure" + }, + { + "startTime": 11231.17, + "name": "lh:computed:TraceEngineResult:parse:Invalidations:finalize", + "duration": 1.35, + "entryType": "measure" + }, + { + "startTime": 11232.55, + "name": "lh:computed:TraceEngineResult:parse:PageLoadMetrics:finalize", + "duration": 2.89, + "entryType": "measure" + }, + { + "startTime": 11235.48, + "name": "lh:computed:TraceEngineResult:parse:LargestImagePaint:finalize", + "duration": 1.71, + "entryType": "measure" + }, + { + "startTime": 11237.24, + "name": "lh:computed:TraceEngineResult:parse:LargestTextPaint:finalize", + "duration": 1.45, + "entryType": "measure" + }, + { + "startTime": 11238.76, + "name": "lh:computed:TraceEngineResult:parse:Screenshots:finalize", + "duration": 2.63, + "entryType": "measure" + }, + { + "startTime": 11241.48, + "name": "lh:computed:TraceEngineResult:parse:LayoutShifts:finalize", + "duration": 2.49, + "entryType": "measure" + }, + { + "startTime": 11244.01, + "name": "lh:computed:TraceEngineResult:parse:Memory:finalize", + "duration": 1.21, + "entryType": "measure" + }, + { + "startTime": 11245.26, + "name": "lh:computed:TraceEngineResult:parse:PageFrames:finalize", + "duration": 1.17, + "entryType": "measure" + }, + { + "startTime": 11246.46, + "name": "lh:computed:TraceEngineResult:parse:Scripts:finalize", + "duration": 2.45, + "entryType": "measure" + }, + { + "startTime": 11248.95, + "name": "lh:computed:TraceEngineResult:parse:SelectorStats:finalize", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 11250.3, + "name": "lh:computed:TraceEngineResult:parse:UserInteractions:finalize", + "duration": 1.69, + "entryType": "measure" + }, + { + "startTime": 11252.07, + "name": "lh:computed:TraceEngineResult:parse:Workers:finalize", + "duration": 1.59, + "entryType": "measure" + }, + { + "startTime": 11253.79, + "name": "lh:computed:TraceEngineResult:parse:Warnings:finalize", + "duration": 2.12, + "entryType": "measure" + }, + { + "startTime": 11256.08, + "name": "lh:computed:TraceEngineResult:parse:clone", + "duration": 3.9, + "entryType": "measure" + }, + { + "startTime": 11260.05, + "name": "lh:computed:TraceEngineResult:insights", + "duration": 186.03, + "entryType": "measure" + }, + { + "startTime": 11260.61, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 0.71, + "entryType": "measure" + }, + { + "startTime": 11261.34, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 11261.68, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 0.71, + "entryType": "measure" + }, + { + "startTime": 11262.43, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.23, + "entryType": "measure" + }, + { + "startTime": 11262.69, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 2.09, + "entryType": "measure" + }, + { + "startTime": 11264.8, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 11265.06, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 11265.36, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 11265.56, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.5, + "entryType": "measure" + }, + { + "startTime": 11266.09, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 11266.29, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 11266.53, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 0.34, + "entryType": "measure" + }, + { + "startTime": 11266.89, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 11267.23, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 11267.45, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 11267.68, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.27, + "entryType": "measure" + }, + { + "startTime": 11267.97, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 2.35, + "entryType": "measure" + }, + { + "startTime": 11270.36, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.31, + "entryType": "measure" + }, + { + "startTime": 11271.13, + "name": "lh:computed:TraceEngineResult:insights:createLanternContext", + "duration": 121.7, + "entryType": "measure" + }, + { + "startTime": 11392.95, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 1.29, + "entryType": "measure" + }, + { + "startTime": 11394.27, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 1.84, + "entryType": "measure" + }, + { + "startTime": 11396.14, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 0.33, + "entryType": "measure" + }, + { + "startTime": 11396.5, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.64, + "entryType": "measure" + }, + { + "startTime": 11397.17, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 0.55, + "entryType": "measure" + }, + { + "startTime": 11397.74, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 11397.94, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.05, + "entryType": "measure" + }, + { + "startTime": 11398, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.03, + "entryType": "measure" + }, + { + "startTime": 11398.05, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 11398.39, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.4, + "entryType": "measure" + }, + { + "startTime": 11398.81, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 0.06, + "entryType": "measure" + }, + { + "startTime": 11398.88, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 11.58, + "entryType": "measure" + }, + { + "startTime": 11410.5, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 6.96, + "entryType": "measure" + }, + { + "startTime": 11417.5, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 6.53, + "entryType": "measure" + }, + { + "startTime": 11424.11, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 1.22, + "entryType": "measure" + }, + { + "startTime": 11425.37, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.08, + "entryType": "measure" + }, + { + "startTime": 11425.46, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 19.29, + "entryType": "measure" + }, + { + "startTime": 11444.78, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.06, + "entryType": "measure" + }, + { + "startTime": 11484.18, + "name": "lh:computed:ProcessedNavigation", + "duration": 1.79, + "entryType": "measure" + }, + { + "startTime": 11486.25, + "name": "lh:computed:CumulativeLayoutShift", + "duration": 11.37, + "entryType": "measure" + }, + { + "startTime": 11498.65, + "name": "lh:computed:Responsiveness", + "duration": 0.15, + "entryType": "measure" + }, + { + "startTime": 11542.19, + "name": "lh:gather:getArtifact:ViewportDimensions", + "duration": 2.29, + "entryType": "measure" + }, + { + "startTime": 11544.53, + "name": "lh:gather:getArtifact:FullPageScreenshot", + "duration": 1257.41, + "entryType": "measure" + }, + { + "startTime": 12802.16, + "name": "lh:gather:getArtifact:BFCacheFailures", + "duration": 281.82, + "entryType": "measure" + }, + { + "startTime": 13208.22, + "name": "lh:runner:audit", + "duration": 2102.75, + "entryType": "measure" + }, + { + "startTime": 13208.45, + "name": "lh:runner:auditing", + "duration": 2101.84, + "entryType": "measure" + }, + { + "startTime": 13210.4, + "name": "lh:audit:is-on-https", + "duration": 2.96, + "entryType": "measure" + }, + { + "startTime": 13213.93, + "name": "lh:audit:redirects-http", + "duration": 2.25, + "entryType": "measure" + }, + { + "startTime": 13216.92, + "name": "lh:audit:viewport", + "duration": 4.83, + "entryType": "measure" + }, + { + "startTime": 13218.25, + "name": "lh:computed:ViewportMeta", + "duration": 1.48, + "entryType": "measure" + }, + { + "startTime": 13222.64, + "name": "lh:audit:first-contentful-paint", + "duration": 54.57, + "entryType": "measure" + }, + { + "startTime": 13223.67, + "name": "lh:computed:FirstContentfulPaint", + "duration": 48.92, + "entryType": "measure" + }, + { + "startTime": 13224.28, + "name": "lh:computed:LanternFirstContentfulPaint", + "duration": 48.25, + "entryType": "measure" + }, + { + "startTime": 13225.25, + "name": "lh:computed:PageDependencyGraph", + "duration": 25.57, + "entryType": "measure" + }, + { + "startTime": 13250.98, + "name": "lh:computed:LoadSimulator", + "duration": 4.47, + "entryType": "measure" + }, + { + "startTime": 13251.4, + "name": "lh:computed:NetworkAnalysis", + "duration": 3.81, + "entryType": "measure" + }, + { + "startTime": 13277.74, + "name": "lh:audit:largest-contentful-paint", + "duration": 28.07, + "entryType": "measure" + }, + { + "startTime": 13278.87, + "name": "lh:computed:LargestContentfulPaint", + "duration": 24.75, + "entryType": "measure" + }, + { + "startTime": 13279.06, + "name": "lh:computed:LanternLargestContentfulPaint", + "duration": 24.53, + "entryType": "measure" + }, + { + "startTime": 13306.35, + "name": "lh:audit:first-meaningful-paint", + "duration": 1.44, + "entryType": "measure" + }, + { + "startTime": 13308.19, + "name": "lh:audit:speed-index", + "duration": 189.79, + "entryType": "measure" + }, + { + "startTime": 13308.68, + "name": "lh:computed:SpeedIndex", + "duration": 187.72, + "entryType": "measure" + }, + { + "startTime": 13308.77, + "name": "lh:computed:LanternSpeedIndex", + "duration": 187.59, + "entryType": "measure" + }, + { + "startTime": 13308.83, + "name": "lh:computed:Speedline", + "duration": 174.49, + "entryType": "measure" + }, + { + "startTime": 13498.04, + "name": "lh:audit:screenshot-thumbnails", + "duration": 0.76, + "entryType": "measure" + }, + { + "startTime": 13498.86, + "name": "lh:audit:final-screenshot", + "duration": 3.15, + "entryType": "measure" + }, + { + "startTime": 13499.03, + "name": "lh:computed:Screenshots", + "duration": 2.83, + "entryType": "measure" + }, + { + "startTime": 13502.74, + "name": "lh:audit:total-blocking-time", + "duration": 19.8, + "entryType": "measure" + }, + { + "startTime": 13503.4, + "name": "lh:computed:TotalBlockingTime", + "duration": 17.83, + "entryType": "measure" + }, + { + "startTime": 13503.56, + "name": "lh:computed:LanternTotalBlockingTime", + "duration": 17.63, + "entryType": "measure" + }, + { + "startTime": 13503.84, + "name": "lh:computed:LanternInteractive", + "duration": 10.33, + "entryType": "measure" + }, + { + "startTime": 13522.83, + "name": "lh:audit:max-potential-fid", + "duration": 7.38, + "entryType": "measure" + }, + { + "startTime": 13523.26, + "name": "lh:computed:MaxPotentialFID", + "duration": 5.11, + "entryType": "measure" + }, + { + "startTime": 13523.35, + "name": "lh:computed:LanternMaxPotentialFID", + "duration": 5, + "entryType": "measure" + }, + { + "startTime": 13530.68, + "name": "lh:audit:cumulative-layout-shift", + "duration": 1.43, + "entryType": "measure" + }, + { + "startTime": 13532.69, + "name": "lh:audit:errors-in-console", + "duration": 159.56, + "entryType": "measure" + }, + { + "startTime": 13533.71, + "name": "lh:computed:JSBundles", + "duration": 154.76, + "entryType": "measure" + }, + { + "startTime": 13693.02, + "name": "lh:audit:server-response-time", + "duration": 4.04, + "entryType": "measure" + }, + { + "startTime": 13697.77, + "name": "lh:audit:interactive", + "duration": 1.88, + "entryType": "measure" + }, + { + "startTime": 13698.33, + "name": "lh:computed:Interactive", + "duration": 0.15, + "entryType": "measure" + }, + { + "startTime": 13699.94, + "name": "lh:audit:user-timings", + "duration": 8.28, + "entryType": "measure" + }, + { + "startTime": 13700.35, + "name": "lh:computed:UserTimings", + "duration": 3.48, + "entryType": "measure" + }, + { + "startTime": 13709.63, + "name": "lh:audit:critical-request-chains", + "duration": 7.83, + "entryType": "measure" + }, + { + "startTime": 13711.12, + "name": "lh:computed:CriticalRequestChains", + "duration": 2.85, + "entryType": "measure" + }, + { + "startTime": 13718.02, + "name": "lh:audit:redirects", + "duration": 4.46, + "entryType": "measure" + }, + { + "startTime": 13722.91, + "name": "lh:audit:image-aspect-ratio", + "duration": 2.04, + "entryType": "measure" + }, + { + "startTime": 13725.32, + "name": "lh:audit:image-size-responsive", + "duration": 2.37, + "entryType": "measure" + }, + { + "startTime": 13725.85, + "name": "lh:computed:ImageRecords", + "duration": 0.38, + "entryType": "measure" + }, + { + "startTime": 13728.06, + "name": "lh:audit:deprecations", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 13729.9, + "name": "lh:audit:third-party-cookies", + "duration": 1.41, + "entryType": "measure" + }, + { + "startTime": 13731.71, + "name": "lh:audit:mainthread-work-breakdown", + "duration": 27.86, + "entryType": "measure" + }, + { + "startTime": 13732.49, + "name": "lh:computed:MainThreadTasks", + "duration": 24.55, + "entryType": "measure" + }, + { + "startTime": 13760, + "name": "lh:audit:bootup-time", + "duration": 22.75, + "entryType": "measure" + }, + { + "startTime": 13763.92, + "name": "lh:computed:TBTImpactTasks", + "duration": 11.79, + "entryType": "measure" + }, + { + "startTime": 13783.22, + "name": "lh:audit:uses-rel-preconnect", + "duration": 5.82, + "entryType": "measure" + }, + { + "startTime": 13789.61, + "name": "lh:audit:font-display", + "duration": 4.01, + "entryType": "measure" + }, + { + "startTime": 13793.65, + "name": "lh:audit:diagnostics", + "duration": 1.28, + "entryType": "measure" + }, + { + "startTime": 13794.96, + "name": "lh:audit:network-requests", + "duration": 8.87, + "entryType": "measure" + }, + { + "startTime": 13795.21, + "name": "lh:computed:EntityClassification", + "duration": 4.33, + "entryType": "measure" + }, + { + "startTime": 13809.59, + "name": "lh:audit:network-rtt", + "duration": 1.72, + "entryType": "measure" + }, + { + "startTime": 13811.62, + "name": "lh:audit:network-server-latency", + "duration": 1.44, + "entryType": "measure" + }, + { + "startTime": 13813.09, + "name": "lh:audit:main-thread-tasks", + "duration": 0.47, + "entryType": "measure" + }, + { + "startTime": 13813.58, + "name": "lh:audit:metrics", + "duration": 5.86, + "entryType": "measure" + }, + { + "startTime": 13813.78, + "name": "lh:computed:TimingSummary", + "duration": 5.27, + "entryType": "measure" + }, + { + "startTime": 13814.19, + "name": "lh:computed:FirstContentfulPaintAllFrames", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 13814.35, + "name": "lh:computed:LargestContentfulPaintAllFrames", + "duration": 0.1, + "entryType": "measure" + }, + { + "startTime": 13814.55, + "name": "lh:computed:LCPBreakdown", + "duration": 3.5, + "entryType": "measure" + }, + { + "startTime": 13814.71, + "name": "lh:computed:TimeToFirstByte", + "duration": 0.33, + "entryType": "measure" + }, + { + "startTime": 13815.08, + "name": "lh:computed:LCPImageRecord", + "duration": 2.89, + "entryType": "measure" + }, + { + "startTime": 13819.49, + "name": "lh:audit:resource-summary", + "duration": 3.7, + "entryType": "measure" + }, + { + "startTime": 13819.86, + "name": "lh:computed:ResourceSummary", + "duration": 1.88, + "entryType": "measure" + }, + { + "startTime": 13823.63, + "name": "lh:audit:third-party-summary", + "duration": 5.13, + "entryType": "measure" + }, + { + "startTime": 13829.19, + "name": "lh:audit:third-party-facades", + "duration": 4.56, + "entryType": "measure" + }, + { + "startTime": 13834.56, + "name": "lh:audit:largest-contentful-paint-element", + "duration": 3.26, + "entryType": "measure" + }, + { + "startTime": 13838.37, + "name": "lh:audit:lcp-lazy-loaded", + "duration": 1.8, + "entryType": "measure" + }, + { + "startTime": 13840.5, + "name": "lh:audit:layout-shifts", + "duration": 2.8, + "entryType": "measure" + }, + { + "startTime": 13843.78, + "name": "lh:audit:long-tasks", + "duration": 12.29, + "entryType": "measure" + }, + { + "startTime": 13856.47, + "name": "lh:audit:non-composited-animations", + "duration": 1.52, + "entryType": "measure" + }, + { + "startTime": 13858.32, + "name": "lh:audit:unsized-images", + "duration": 1.72, + "entryType": "measure" + }, + { + "startTime": 13860.39, + "name": "lh:audit:valid-source-maps", + "duration": 2.8, + "entryType": "measure" + }, + { + "startTime": 13863.49, + "name": "lh:audit:prioritize-lcp-image", + "duration": 1.05, + "entryType": "measure" + }, + { + "startTime": 13864.87, + "name": "lh:audit:csp-xss", + "duration": 2.52, + "entryType": "measure" + }, + { + "startTime": 13867.97, + "name": "lh:audit:has-hsts", + "duration": 2.38, + "entryType": "measure" + }, + { + "startTime": 13871.01, + "name": "lh:audit:origin-isolation", + "duration": 3.39, + "entryType": "measure" + }, + { + "startTime": 13874.71, + "name": "lh:audit:clickjacking-mitigation", + "duration": 1.46, + "entryType": "measure" + }, + { + "startTime": 13876.54, + "name": "lh:audit:trusted-types-xss", + "duration": 1.46, + "entryType": "measure" + }, + { + "startTime": 13878.03, + "name": "lh:audit:script-treemap-data", + "duration": 175.48, + "entryType": "measure" + }, + { + "startTime": 13878.46, + "name": "lh:computed:ModuleDuplication", + "duration": 1.68, + "entryType": "measure" + }, + { + "startTime": 13880.6, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.61, + "entryType": "measure" + }, + { + "startTime": 13881.4, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.13, + "entryType": "measure" + }, + { + "startTime": 13881.6, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 13.79, + "entryType": "measure" + }, + { + "startTime": 13896.11, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 11.3, + "entryType": "measure" + }, + { + "startTime": 13907.59, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.3, + "entryType": "measure" + }, + { + "startTime": 13908.11, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.19, + "entryType": "measure" + }, + { + "startTime": 13908.52, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 5.44, + "entryType": "measure" + }, + { + "startTime": 13914.57, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.46, + "entryType": "measure" + }, + { + "startTime": 13915.25, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 43.58, + "entryType": "measure" + }, + { + "startTime": 13959.05, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.42, + "entryType": "measure" + }, + { + "startTime": 13960.59, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.15, + "entryType": "measure" + }, + { + "startTime": 13960.87, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 13961.35, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.53, + "entryType": "measure" + }, + { + "startTime": 13962.11, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 31.93, + "entryType": "measure" + }, + { + "startTime": 13994.32, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.31, + "entryType": "measure" + }, + { + "startTime": 13994.81, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 13995.31, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 13995.69, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.34, + "entryType": "measure" + }, + { + "startTime": 13996.22, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.37, + "entryType": "measure" + }, + { + "startTime": 13996.78, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.2, + "entryType": "measure" + }, + { + "startTime": 13997.17, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.4, + "entryType": "measure" + }, + { + "startTime": 13997.83, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.27, + "entryType": "measure" + }, + { + "startTime": 13998.28, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.2, + "entryType": "measure" + }, + { + "startTime": 13998.71, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.54, + "entryType": "measure" + }, + { + "startTime": 13999.86, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.94, + "entryType": "measure" + }, + { + "startTime": 14001.96, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.7, + "entryType": "measure" + }, + { + "startTime": 14003.03, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 4.35, + "entryType": "measure" + }, + { + "startTime": 14007.92, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.57, + "entryType": "measure" + }, + { + "startTime": 14008.74, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.27, + "entryType": "measure" + }, + { + "startTime": 14009.16, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 14009.62, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.29, + "entryType": "measure" + }, + { + "startTime": 14010.1, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 14010.51, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.47, + "entryType": "measure" + }, + { + "startTime": 14011.34, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 14012.04, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.5, + "entryType": "measure" + }, + { + "startTime": 14012.76, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.14, + "entryType": "measure" + }, + { + "startTime": 14013.17, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 14013.62, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.15, + "entryType": "measure" + }, + { + "startTime": 14013.92, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 14014.36, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.73, + "entryType": "measure" + }, + { + "startTime": 14015.64, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.5, + "entryType": "measure" + }, + { + "startTime": 14016.51, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.25, + "entryType": "measure" + }, + { + "startTime": 14017.8, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.54, + "entryType": "measure" + }, + { + "startTime": 14018.89, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.59, + "entryType": "measure" + }, + { + "startTime": 14019.73, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 14020.37, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.48, + "entryType": "measure" + }, + { + "startTime": 14022.53, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 14023.17, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.56, + "entryType": "measure" + }, + { + "startTime": 14024, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.25, + "entryType": "measure" + }, + { + "startTime": 14024.55, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.8, + "entryType": "measure" + }, + { + "startTime": 14025.55, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 14025.85, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.49, + "entryType": "measure" + }, + { + "startTime": 14026.56, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 14026.96, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 14027.33, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.3, + "entryType": "measure" + }, + { + "startTime": 14027.89, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.46, + "entryType": "measure" + }, + { + "startTime": 14028.5, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 14028.97, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.27, + "entryType": "measure" + }, + { + "startTime": 14029.48, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.98, + "entryType": "measure" + }, + { + "startTime": 14031.67, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.2, + "entryType": "measure" + }, + { + "startTime": 14032.23, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.34, + "entryType": "measure" + }, + { + "startTime": 14032.8, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.07, + "entryType": "measure" + }, + { + "startTime": 14034.24, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.45, + "entryType": "measure" + }, + { + "startTime": 14035, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.39, + "entryType": "measure" + }, + { + "startTime": 14035.67, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.62, + "entryType": "measure" + }, + { + "startTime": 14036.54, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.62, + "entryType": "measure" + }, + { + "startTime": 14037.4, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.27, + "entryType": "measure" + }, + { + "startTime": 14037.81, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 14038.29, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 14038.8, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.14, + "entryType": "measure" + }, + { + "startTime": 14039.04, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.19, + "entryType": "measure" + }, + { + "startTime": 14039.53, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.39, + "entryType": "measure" + }, + { + "startTime": 14040.14, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.56, + "entryType": "measure" + }, + { + "startTime": 14040.93, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.54, + "entryType": "measure" + }, + { + "startTime": 14041.69, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 14041.92, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 14042.24, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.3, + "entryType": "measure" + }, + { + "startTime": 14042.78, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 14043.02, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 14043.38, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.85, + "entryType": "measure" + }, + { + "startTime": 14044.57, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.42, + "entryType": "measure" + }, + { + "startTime": 14045.25, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.31, + "entryType": "measure" + }, + { + "startTime": 14046.66, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.15, + "entryType": "measure" + }, + { + "startTime": 14047.04, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.46, + "entryType": "measure" + }, + { + "startTime": 14047.84, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.61, + "entryType": "measure" + }, + { + "startTime": 14048.86, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 14050.36, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 14050.75, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.37, + "entryType": "measure" + }, + { + "startTime": 14051.27, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.13, + "entryType": "measure" + }, + { + "startTime": 14051.62, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.41, + "entryType": "measure" + }, + { + "startTime": 14052.26, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 14052.85, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.49, + "entryType": "measure" + }, + { + "startTime": 14054.37, + "name": "lh:audit:accesskeys", + "duration": 3.41, + "entryType": "measure" + }, + { + "startTime": 14058.45, + "name": "lh:audit:aria-allowed-attr", + "duration": 5.8, + "entryType": "measure" + }, + { + "startTime": 14064.74, + "name": "lh:audit:aria-allowed-role", + "duration": 7.42, + "entryType": "measure" + }, + { + "startTime": 14073.03, + "name": "lh:audit:aria-command-name", + "duration": 2.69, + "entryType": "measure" + }, + { + "startTime": 14076.29, + "name": "lh:audit:aria-conditional-attr", + "duration": 5.89, + "entryType": "measure" + }, + { + "startTime": 14082.64, + "name": "lh:audit:aria-deprecated-role", + "duration": 7.35, + "entryType": "measure" + }, + { + "startTime": 14090.69, + "name": "lh:audit:aria-dialog-name", + "duration": 2.03, + "entryType": "measure" + }, + { + "startTime": 14093.21, + "name": "lh:audit:aria-hidden-body", + "duration": 4.48, + "entryType": "measure" + }, + { + "startTime": 14098.03, + "name": "lh:audit:aria-hidden-focus", + "duration": 5.32, + "entryType": "measure" + }, + { + "startTime": 14103.72, + "name": "lh:audit:aria-input-field-name", + "duration": 2.87, + "entryType": "measure" + }, + { + "startTime": 14106.93, + "name": "lh:audit:aria-meter-name", + "duration": 1.45, + "entryType": "measure" + }, + { + "startTime": 14108.9, + "name": "lh:audit:aria-progressbar-name", + "duration": 1.6, + "entryType": "measure" + }, + { + "startTime": 14110.86, + "name": "lh:audit:aria-prohibited-attr", + "duration": 3.73, + "entryType": "measure" + }, + { + "startTime": 14114.9, + "name": "lh:audit:aria-required-attr", + "duration": 4.53, + "entryType": "measure" + }, + { + "startTime": 14119.8, + "name": "lh:audit:aria-required-children", + "duration": 6.1, + "entryType": "measure" + }, + { + "startTime": 14126.25, + "name": "lh:audit:aria-required-parent", + "duration": 1.71, + "entryType": "measure" + }, + { + "startTime": 14128.24, + "name": "lh:audit:aria-roles", + "duration": 3.5, + "entryType": "measure" + }, + { + "startTime": 14132.06, + "name": "lh:audit:aria-text", + "duration": 2.4, + "entryType": "measure" + }, + { + "startTime": 14134.92, + "name": "lh:audit:aria-toggle-field-name", + "duration": 2.07, + "entryType": "measure" + }, + { + "startTime": 14137.61, + "name": "lh:audit:aria-tooltip-name", + "duration": 2.54, + "entryType": "measure" + }, + { + "startTime": 14140.59, + "name": "lh:audit:aria-treeitem-name", + "duration": 2.39, + "entryType": "measure" + }, + { + "startTime": 14143.28, + "name": "lh:audit:aria-valid-attr-value", + "duration": 3.91, + "entryType": "measure" + }, + { + "startTime": 14147.51, + "name": "lh:audit:aria-valid-attr", + "duration": 4.4, + "entryType": "measure" + }, + { + "startTime": 14152.35, + "name": "lh:audit:button-name", + "duration": 4.4, + "entryType": "measure" + }, + { + "startTime": 14157.07, + "name": "lh:audit:bypass", + "duration": 4.15, + "entryType": "measure" + }, + { + "startTime": 14161.66, + "name": "lh:audit:color-contrast", + "duration": 3.96, + "entryType": "measure" + }, + { + "startTime": 14166.04, + "name": "lh:audit:definition-list", + "duration": 12.99, + "entryType": "measure" + }, + { + "startTime": 14179.7, + "name": "lh:audit:dlitem", + "duration": 3.21, + "entryType": "measure" + }, + { + "startTime": 14183.25, + "name": "lh:audit:document-title", + "duration": 4.87, + "entryType": "measure" + }, + { + "startTime": 14188.75, + "name": "lh:audit:duplicate-id-aria", + "duration": 4.9, + "entryType": "measure" + }, + { + "startTime": 14193.98, + "name": "lh:audit:empty-heading", + "duration": 4.37, + "entryType": "measure" + }, + { + "startTime": 14198.89, + "name": "lh:audit:form-field-multiple-labels", + "duration": 4.57, + "entryType": "measure" + }, + { + "startTime": 14204.17, + "name": "lh:audit:frame-title", + "duration": 2.93, + "entryType": "measure" + }, + { + "startTime": 14207.65, + "name": "lh:audit:heading-order", + "duration": 4.15, + "entryType": "measure" + }, + { + "startTime": 14212.27, + "name": "lh:audit:html-has-lang", + "duration": 5.3, + "entryType": "measure" + }, + { + "startTime": 14218, + "name": "lh:audit:html-lang-valid", + "duration": 4.99, + "entryType": "measure" + }, + { + "startTime": 14223.46, + "name": "lh:audit:html-xml-lang-mismatch", + "duration": 2.87, + "entryType": "measure" + }, + { + "startTime": 14226.73, + "name": "lh:audit:identical-links-same-purpose", + "duration": 12.08, + "entryType": "measure" + }, + { + "startTime": 14239.16, + "name": "lh:audit:image-alt", + "duration": 4.18, + "entryType": "measure" + }, + { + "startTime": 14243.68, + "name": "lh:audit:image-redundant-alt", + "duration": 4.08, + "entryType": "measure" + }, + { + "startTime": 14248.09, + "name": "lh:audit:input-button-name", + "duration": 2.82, + "entryType": "measure" + }, + { + "startTime": 14251.35, + "name": "lh:audit:input-image-alt", + "duration": 3.19, + "entryType": "measure" + }, + { + "startTime": 14255.18, + "name": "lh:audit:label-content-name-mismatch", + "duration": 4.41, + "entryType": "measure" + }, + { + "startTime": 14260.06, + "name": "lh:audit:label", + "duration": 4.26, + "entryType": "measure" + }, + { + "startTime": 14264.74, + "name": "lh:audit:landmark-one-main", + "duration": 4.97, + "entryType": "measure" + }, + { + "startTime": 14270.25, + "name": "lh:audit:link-name", + "duration": 5.12, + "entryType": "measure" + }, + { + "startTime": 14275.7, + "name": "lh:audit:link-in-text-block", + "duration": 2.99, + "entryType": "measure" + }, + { + "startTime": 14279.01, + "name": "lh:audit:list", + "duration": 15.66, + "entryType": "measure" + }, + { + "startTime": 14295.03, + "name": "lh:audit:listitem", + "duration": 5.29, + "entryType": "measure" + }, + { + "startTime": 14300.81, + "name": "lh:audit:meta-refresh", + "duration": 3.45, + "entryType": "measure" + }, + { + "startTime": 14304.62, + "name": "lh:audit:meta-viewport", + "duration": 4.67, + "entryType": "measure" + }, + { + "startTime": 14309.76, + "name": "lh:audit:object-alt", + "duration": 3.14, + "entryType": "measure" + }, + { + "startTime": 14313.2, + "name": "lh:audit:select-name", + "duration": 2.91, + "entryType": "measure" + }, + { + "startTime": 14316.41, + "name": "lh:audit:skip-link", + "duration": 5.64, + "entryType": "measure" + }, + { + "startTime": 14322.66, + "name": "lh:audit:tabindex", + "duration": 4.68, + "entryType": "measure" + }, + { + "startTime": 14327.75, + "name": "lh:audit:table-duplicate-name", + "duration": 3.3, + "entryType": "measure" + }, + { + "startTime": 14331.39, + "name": "lh:audit:table-fake-caption", + "duration": 4.45, + "entryType": "measure" + }, + { + "startTime": 14336.16, + "name": "lh:audit:target-size", + "duration": 16.36, + "entryType": "measure" + }, + { + "startTime": 14353.09, + "name": "lh:audit:td-has-header", + "duration": 6.31, + "entryType": "measure" + }, + { + "startTime": 14360.04, + "name": "lh:audit:td-headers-attr", + "duration": 6.54, + "entryType": "measure" + }, + { + "startTime": 14367.5, + "name": "lh:audit:th-has-data-cells", + "duration": 7.14, + "entryType": "measure" + }, + { + "startTime": 14375.17, + "name": "lh:audit:valid-lang", + "duration": 6.56, + "entryType": "measure" + }, + { + "startTime": 14382.26, + "name": "lh:audit:video-caption", + "duration": 6.86, + "entryType": "measure" + }, + { + "startTime": 14389.25, + "name": "lh:audit:custom-controls-labels", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 14389.48, + "name": "lh:audit:custom-controls-roles", + "duration": 0.03, + "entryType": "measure" + }, + { + "startTime": 14389.53, + "name": "lh:audit:focus-traps", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14389.56, + "name": "lh:audit:focusable-controls", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14389.6, + "name": "lh:audit:interactive-element-affordance", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14389.63, + "name": "lh:audit:logical-tab-order", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14389.66, + "name": "lh:audit:managed-focus", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14389.69, + "name": "lh:audit:offscreen-content-hidden", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14389.73, + "name": "lh:audit:use-landmarks", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14389.77, + "name": "lh:audit:visual-order-follows-dom", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 14390.36, + "name": "lh:audit:uses-long-cache-ttl", + "duration": 4.12, + "entryType": "measure" + }, + { + "startTime": 14394.99, + "name": "lh:audit:total-byte-weight", + "duration": 2.75, + "entryType": "measure" + }, + { + "startTime": 14398.15, + "name": "lh:audit:offscreen-images", + "duration": 10.73, + "entryType": "measure" + }, + { + "startTime": 14409.27, + "name": "lh:audit:render-blocking-resources", + "duration": 28.7, + "entryType": "measure" + }, + { + "startTime": 14410.05, + "name": "lh:computed:UnusedCSS", + "duration": 25.93, + "entryType": "measure" + }, + { + "startTime": 14436.13, + "name": "lh:computed:NavigationInsights", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 14436.49, + "name": "lh:computed:FirstContentfulPaint", + "duration": 0.1, + "entryType": "measure" + }, + { + "startTime": 14438.44, + "name": "lh:audit:unminified-css", + "duration": 38.65, + "entryType": "measure" + }, + { + "startTime": 14477.58, + "name": "lh:audit:unminified-javascript", + "duration": 212.65, + "entryType": "measure" + }, + { + "startTime": 14690.73, + "name": "lh:audit:unused-css-rules", + "duration": 7.3, + "entryType": "measure" + }, + { + "startTime": 14698.32, + "name": "lh:audit:unused-javascript", + "duration": 25.07, + "entryType": "measure" + }, + { + "startTime": 14723.7, + "name": "lh:audit:modern-image-formats", + "duration": 4.35, + "entryType": "measure" + }, + { + "startTime": 14728.26, + "name": "lh:audit:uses-optimized-images", + "duration": 5.65, + "entryType": "measure" + }, + { + "startTime": 14734.35, + "name": "lh:audit:uses-text-compression", + "duration": 7.41, + "entryType": "measure" + }, + { + "startTime": 14741.97, + "name": "lh:audit:uses-responsive-images", + "duration": 7.14, + "entryType": "measure" + }, + { + "startTime": 14749.47, + "name": "lh:audit:efficient-animated-content", + "duration": 7.17, + "entryType": "measure" + }, + { + "startTime": 14756.88, + "name": "lh:audit:duplicated-javascript", + "duration": 4.55, + "entryType": "measure" + }, + { + "startTime": 14761.68, + "name": "lh:audit:legacy-javascript", + "duration": 427.95, + "entryType": "measure" + }, + { + "startTime": 15190.1, + "name": "lh:audit:doctype", + "duration": 1.41, + "entryType": "measure" + }, + { + "startTime": 15192.07, + "name": "lh:audit:charset", + "duration": 1.61, + "entryType": "measure" + }, + { + "startTime": 15194, + "name": "lh:audit:dom-size", + "duration": 2.17, + "entryType": "measure" + }, + { + "startTime": 15196.56, + "name": "lh:audit:geolocation-on-start", + "duration": 1.26, + "entryType": "measure" + }, + { + "startTime": 15198.09, + "name": "lh:audit:inspector-issues", + "duration": 1.35, + "entryType": "measure" + }, + { + "startTime": 15200.07, + "name": "lh:audit:no-document-write", + "duration": 2.3, + "entryType": "measure" + }, + { + "startTime": 15202.63, + "name": "lh:audit:js-libraries", + "duration": 1.29, + "entryType": "measure" + }, + { + "startTime": 15204.7, + "name": "lh:audit:notification-on-start", + "duration": 2.17, + "entryType": "measure" + }, + { + "startTime": 15207.29, + "name": "lh:audit:paste-preventing-inputs", + "duration": 1.75, + "entryType": "measure" + }, + { + "startTime": 15209.26, + "name": "lh:audit:uses-http2", + "duration": 12.96, + "entryType": "measure" + }, + { + "startTime": 15222.67, + "name": "lh:audit:uses-passive-event-listeners", + "duration": 1.84, + "entryType": "measure" + }, + { + "startTime": 15224.92, + "name": "lh:audit:meta-description", + "duration": 1.2, + "entryType": "measure" + }, + { + "startTime": 15226.39, + "name": "lh:audit:http-status-code", + "duration": 1.05, + "entryType": "measure" + }, + { + "startTime": 15227.73, + "name": "lh:audit:font-size", + "duration": 1.66, + "entryType": "measure" + }, + { + "startTime": 15229.73, + "name": "lh:audit:link-text", + "duration": 1.82, + "entryType": "measure" + }, + { + "startTime": 15232.04, + "name": "lh:audit:crawlable-anchors", + "duration": 2.64, + "entryType": "measure" + }, + { + "startTime": 15235.26, + "name": "lh:audit:is-crawlable", + "duration": 3.45, + "entryType": "measure" + }, + { + "startTime": 15239.21, + "name": "lh:audit:robots-txt", + "duration": 3.07, + "entryType": "measure" + }, + { + "startTime": 15242.68, + "name": "lh:audit:hreflang", + "duration": 1.39, + "entryType": "measure" + }, + { + "startTime": 15244.37, + "name": "lh:audit:canonical", + "duration": 1.49, + "entryType": "measure" + }, + { + "startTime": 15246.1, + "name": "lh:audit:structured-data", + "duration": 0.73, + "entryType": "measure" + }, + { + "startTime": 15247.24, + "name": "lh:audit:bf-cache", + "duration": 2.47, + "entryType": "measure" + }, + { + "startTime": 15250.56, + "name": "lh:audit:cache-insight", + "duration": 4.01, + "entryType": "measure" + }, + { + "startTime": 15255.15, + "name": "lh:audit:cls-culprits-insight", + "duration": 3.29, + "entryType": "measure" + }, + { + "startTime": 15258.75, + "name": "lh:audit:document-latency-insight", + "duration": 1.42, + "entryType": "measure" + }, + { + "startTime": 15260.5, + "name": "lh:audit:dom-size-insight", + "duration": 1.52, + "entryType": "measure" + }, + { + "startTime": 15262.6, + "name": "lh:audit:duplicated-javascript-insight", + "duration": 2.21, + "entryType": "measure" + }, + { + "startTime": 15265.09, + "name": "lh:audit:font-display-insight", + "duration": 1.4, + "entryType": "measure" + }, + { + "startTime": 15266.84, + "name": "lh:audit:forced-reflow-insight", + "duration": 2.57, + "entryType": "measure" + }, + { + "startTime": 15269.74, + "name": "lh:audit:image-delivery-insight", + "duration": 2.18, + "entryType": "measure" + }, + { + "startTime": 15272.37, + "name": "lh:audit:inp-breakdown-insight", + "duration": 2.12, + "entryType": "measure" + }, + { + "startTime": 15275, + "name": "lh:audit:lcp-breakdown-insight", + "duration": 1.91, + "entryType": "measure" + }, + { + "startTime": 15277.18, + "name": "lh:audit:lcp-discovery-insight", + "duration": 1.07, + "entryType": "measure" + }, + { + "startTime": 15278.6, + "name": "lh:audit:legacy-javascript-insight", + "duration": 1.42, + "entryType": "measure" + }, + { + "startTime": 15280.41, + "name": "lh:audit:modern-http-insight", + "duration": 1.07, + "entryType": "measure" + }, + { + "startTime": 15281.81, + "name": "lh:audit:network-dependency-tree-insight", + "duration": 2.24, + "entryType": "measure" + }, + { + "startTime": 15284.62, + "name": "lh:audit:render-blocking-insight", + "duration": 1.71, + "entryType": "measure" + }, + { + "startTime": 15286.8, + "name": "lh:audit:third-parties-insight", + "duration": 22.1, + "entryType": "measure" + }, + { + "startTime": 15309.2, + "name": "lh:audit:viewport-insight", + "duration": 1.07, + "entryType": "measure" + }, + { + "startTime": 15310.31, + "name": "lh:runner:generate", + "duration": 0.65, + "entryType": "measure" + } + ], + "total": 12327.53 + }, + "i18n": { + "rendererFormattedStrings": { + "calculatorLink": "See calculator.", + "collapseView": "Collapse view", + "crcInitialNavigation": "Initial Navigation", + "crcLongestDurationLabel": "Maximum critical path latency:", + "dropdownCopyJSON": "Copy JSON", + "dropdownDarkTheme": "Toggle Dark Theme", + "dropdownPrintExpanded": "Print Expanded", + "dropdownPrintSummary": "Print Summary", + "dropdownSaveGist": "Save as Gist", + "dropdownSaveHTML": "Save as HTML", + "dropdownSaveJSON": "Save as JSON", + "dropdownViewUnthrottledTrace": "View Unthrottled Trace", + "dropdownViewer": "Open in Viewer", + "errorLabel": "Error!", + "errorMissingAuditInfo": "Report error: no audit information", + "expandView": "Expand view", + "firstPartyChipLabel": "1st party", + "footerIssue": "File an issue", + "goBackToAudits": "Go back to audits", + "hide": "Hide", + "insightsNotice": "Later this year, insights will replace performance audits. [Learn more and provide feedback here](https://github.com/GoogleChrome/lighthouse/discussions/16462).", + "labDataTitle": "Lab Data", + "lsPerformanceCategoryDescription": "[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.", + "manualAuditsGroupTitle": "Additional items to manually check", + "notApplicableAuditsGroupTitle": "Not applicable", + "openInANewTabTooltip": "Open in a new tab", + "opportunityResourceColumnLabel": "Opportunity", + "opportunitySavingsColumnLabel": "Estimated Savings", + "passedAuditsGroupTitle": "Passed audits", + "runtimeAnalysisWindow": "Initial page load", + "runtimeAnalysisWindowSnapshot": "Point-in-time snapshot", + "runtimeAnalysisWindowTimespan": "User interactions timespan", + "runtimeCustom": "Custom throttling", + "runtimeDesktopEmulation": "Emulated Desktop", + "runtimeMobileEmulation": "Emulated Moto G Power", + "runtimeNoEmulation": "No emulation", + "runtimeSettingsAxeVersion": "Axe version", + "runtimeSettingsBenchmark": "Unthrottled CPU/Memory Power", + "runtimeSettingsCPUThrottling": "CPU throttling", + "runtimeSettingsDevice": "Device", + "runtimeSettingsNetworkThrottling": "Network throttling", + "runtimeSettingsScreenEmulation": "Screen emulation", + "runtimeSettingsUANetwork": "User agent (network)", + "runtimeSingleLoad": "Single page session", + "runtimeSingleLoadTooltip": "This data is taken from a single page session, as opposed to field data summarizing many sessions.", + "runtimeSlow4g": "Slow 4G throttling", + "runtimeUnknown": "Unknown", + "show": "Show", + "showRelevantAudits": "Show audits relevant to:", + "snippetCollapseButtonLabel": "Collapse snippet", + "snippetExpandButtonLabel": "Expand snippet", + "thirdPartyResourcesLabel": "Show 3rd-party resources", + "throttlingProvided": "Provided by environment", + "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", + "tryInsights": "Try insights", + "unattributable": "Unattributable", + "varianceDisclaimer": "Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.", + "viewTraceLabel": "View Trace", + "viewTreemapLabel": "View Treemap", + "warningAuditsGroupTitle": "Passed audits but with warnings", + "warningHeader": "Warnings: " + }, + "icuMessagePaths": { + "core/audits/is-on-https.js | title": ["audits[is-on-https].title"], + "core/audits/is-on-https.js | description": [ + "audits[is-on-https].description" + ], + "core/audits/is-on-https.js | columnInsecureURL": [ + "audits[is-on-https].details.headings[0].label" + ], + "core/audits/is-on-https.js | columnResolution": [ + "audits[is-on-https].details.headings[1].label" + ], + "core/audits/redirects-http.js | title": ["audits[redirects-http].title"], + "core/audits/redirects-http.js | description": [ + "audits[redirects-http].description" + ], + "core/audits/viewport.js | title": ["audits.viewport.title"], + "core/audits/viewport.js | description": ["audits.viewport.description"], + "core/lib/i18n/i18n.js | firstContentfulPaintMetric": [ + "audits[first-contentful-paint].title" + ], + "core/audits/metrics/first-contentful-paint.js | description": [ + "audits[first-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | seconds": [ + { + "values": { + "timeInMs": 10536.3793 + }, + "path": "audits[first-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 18092.172400000014 + }, + "path": "audits[largest-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 10536.3793 + }, + "path": "audits[speed-index].displayValue" + }, + { + "values": { + "timeInMs": 18302.87930000001 + }, + "path": "audits.interactive.displayValue" + }, + { + "values": { + "timeInMs": 2577.127999999985 + }, + "path": "audits[mainthread-work-breakdown].displayValue" + }, + { + "values": { + "timeInMs": 640.5039999999997 + }, + "path": "audits[bootup-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | largestContentfulPaintMetric": [ + "audits[largest-contentful-paint].title" + ], + "core/audits/metrics/largest-contentful-paint.js | description": [ + "audits[largest-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | firstMeaningfulPaintMetric": [ + "audits[first-meaningful-paint].title" + ], + "core/audits/metrics/first-meaningful-paint.js | description": [ + "audits[first-meaningful-paint].description" + ], + "core/lib/i18n/i18n.js | speedIndexMetric": ["audits[speed-index].title"], + "core/audits/metrics/speed-index.js | description": [ + "audits[speed-index].description" + ], + "core/lib/i18n/i18n.js | totalBlockingTimeMetric": [ + "audits[total-blocking-time].title" + ], + "core/audits/metrics/total-blocking-time.js | description": [ + "audits[total-blocking-time].description" + ], + "core/lib/i18n/i18n.js | ms": [ + { + "values": { + "timeInMs": 237.85345000000234 + }, + "path": "audits[total-blocking-time].displayValue" + }, + { + "values": { + "timeInMs": 142 + }, + "path": "audits[max-potential-fid].displayValue" + }, + { + "values": { + "timeInMs": 0.0609 + }, + "path": "audits[network-rtt].displayValue" + }, + { + "values": { + "timeInMs": 4.2930999999999955 + }, + "path": "audits[network-server-latency].displayValue" + }, + { + "values": { + "timeInMs": 18092.172400000014 + }, + "path": "audits[largest-contentful-paint-element].displayValue" + } + ], + "core/lib/i18n/i18n.js | maxPotentialFIDMetric": [ + "audits[max-potential-fid].title" + ], + "core/audits/metrics/max-potential-fid.js | description": [ + "audits[max-potential-fid].description" + ], + "core/lib/i18n/i18n.js | cumulativeLayoutShiftMetric": [ + "audits[cumulative-layout-shift].title" + ], + "core/audits/metrics/cumulative-layout-shift.js | description": [ + "audits[cumulative-layout-shift].description" + ], + "core/audits/errors-in-console.js | failureTitle": [ + "audits[errors-in-console].title" + ], + "core/audits/errors-in-console.js | description": [ + "audits[errors-in-console].description" + ], + "core/lib/i18n/i18n.js | columnSource": [ + "audits[errors-in-console].details.headings[0].label", + "audits.deprecations.details.headings[1].label", + "audits[geolocation-on-start].details.headings[0].label", + "audits[no-document-write].details.headings[0].label", + "audits[notification-on-start].details.headings[0].label", + "audits[uses-passive-event-listeners].details.headings[0].label", + "audits[font-size].details.headings[0].label", + "audits[forced-reflow-insight].details.items[0].headings[0].label" + ], + "core/lib/i18n/i18n.js | columnDescription": [ + "audits[errors-in-console].details.headings[1].label", + "audits[csp-xss].details.headings[0].label", + "audits[has-hsts].details.headings[0].label", + "audits[origin-isolation].details.headings[0].label", + "audits[clickjacking-mitigation].details.headings[0].label", + "audits[trusted-types-xss].details.headings[0].label" + ], + "core/audits/server-response-time.js | title": [ + "audits[server-response-time].title" + ], + "core/audits/server-response-time.js | description": [ + "audits[server-response-time].description" + ], + "core/audits/server-response-time.js | displayValue": [ + { + "values": { + "timeInMs": 5.055999999999999 + }, + "path": "audits[server-response-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnURL": [ + "audits[server-response-time].details.headings[0].label", + "audits[image-aspect-ratio].details.headings[1].label", + "audits[image-size-responsive].details.headings[1].label", + "audits[third-party-cookies].details.headings[1].label", + "audits[bootup-time].details.headings[0].label", + "audits[font-display].details.headings[0].label", + "audits[network-rtt].details.headings[0].label", + "audits[network-server-latency].details.headings[0].label", + "audits[long-tasks].details.headings[0].label", + "audits[unsized-images].details.headings[1].label", + "audits[valid-source-maps].details.headings[0].label", + "audits[uses-long-cache-ttl].details.headings[0].label", + "audits[total-byte-weight].details.headings[0].label", + "audits[unminified-javascript].details.headings[0].label", + "audits[unused-javascript].details.headings[0].label", + "audits[uses-text-compression].details.headings[0].label", + "audits[font-display-insight].details.headings[0].label", + "audits[image-delivery-insight].details.headings[0].label", + "audits[legacy-javascript-insight].details.headings[0].label", + "audits[modern-http-insight].details.headings[0].label", + "audits[render-blocking-insight].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnTimeSpent": [ + "audits[server-response-time].details.headings[1].label", + "audits[mainthread-work-breakdown].details.headings[1].label", + "audits[network-rtt].details.headings[1].label", + "audits[network-server-latency].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | interactiveMetric": ["audits.interactive.title"], + "core/audits/metrics/interactive.js | description": [ + "audits.interactive.description" + ], + "core/audits/user-timings.js | title": ["audits[user-timings].title"], + "core/audits/user-timings.js | description": [ + "audits[user-timings].description" + ], + "core/audits/user-timings.js | displayValue": [ + { + "values": { + "itemCount": 16 + }, + "path": "audits[user-timings].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnName": [ + "audits[user-timings].details.headings[0].label", + "audits[third-party-cookies].details.headings[0].label" + ], + "core/audits/user-timings.js | columnType": [ + "audits[user-timings].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnStartTime": [ + "audits[user-timings].details.headings[2].label", + "audits[long-tasks].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnDuration": [ + "audits[user-timings].details.headings[3].label", + "audits[long-tasks].details.headings[2].label", + "audits[lcp-breakdown-insight].details.items[0].headings[1].label", + "audits[render-blocking-insight].details.headings[2].label" + ], + "core/audits/critical-request-chains.js | title": [ + "audits[critical-request-chains].title" + ], + "core/audits/critical-request-chains.js | description": [ + "audits[critical-request-chains].description" + ], + "core/audits/critical-request-chains.js | displayValue": [ + { + "values": { + "itemCount": 55 + }, + "path": "audits[critical-request-chains].displayValue" + } + ], + "core/audits/redirects.js | title": ["audits.redirects.title"], + "core/audits/redirects.js | description": [ + "audits.redirects.description" + ], + "core/audits/image-aspect-ratio.js | title": [ + "audits[image-aspect-ratio].title" + ], + "core/audits/image-aspect-ratio.js | description": [ + "audits[image-aspect-ratio].description" + ], + "core/audits/image-aspect-ratio.js | columnDisplayed": [ + "audits[image-aspect-ratio].details.headings[2].label" + ], + "core/audits/image-aspect-ratio.js | columnActual": [ + "audits[image-aspect-ratio].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | title": [ + "audits[image-size-responsive].title" + ], + "core/audits/image-size-responsive.js | description": [ + "audits[image-size-responsive].description" + ], + "core/audits/image-size-responsive.js | columnDisplayed": [ + "audits[image-size-responsive].details.headings[2].label" + ], + "core/audits/image-size-responsive.js | columnActual": [ + "audits[image-size-responsive].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | columnExpected": [ + "audits[image-size-responsive].details.headings[4].label" + ], + "core/audits/deprecations.js | title": ["audits.deprecations.title"], + "core/audits/deprecations.js | description": [ + "audits.deprecations.description" + ], + "core/audits/deprecations.js | columnDeprecate": [ + "audits.deprecations.details.headings[0].label" + ], + "core/audits/third-party-cookies.js | title": [ + "audits[third-party-cookies].title" + ], + "core/audits/third-party-cookies.js | description": [ + "audits[third-party-cookies].description" + ], + "core/audits/mainthread-work-breakdown.js | failureTitle": [ + "audits[mainthread-work-breakdown].title" + ], + "core/audits/mainthread-work-breakdown.js | description": [ + "audits[mainthread-work-breakdown].description" + ], + "core/audits/mainthread-work-breakdown.js | columnCategory": [ + "audits[mainthread-work-breakdown].details.headings[0].label" + ], + "core/audits/bootup-time.js | title": ["audits[bootup-time].title"], + "core/audits/bootup-time.js | description": [ + "audits[bootup-time].description" + ], + "core/audits/bootup-time.js | columnTotal": [ + "audits[bootup-time].details.headings[1].label" + ], + "core/audits/bootup-time.js | columnScriptEval": [ + "audits[bootup-time].details.headings[2].label" + ], + "core/audits/bootup-time.js | columnScriptParse": [ + "audits[bootup-time].details.headings[3].label" + ], + "core/audits/uses-rel-preconnect.js | title": [ + "audits[uses-rel-preconnect].title" + ], + "core/audits/uses-rel-preconnect.js | description": [ + "audits[uses-rel-preconnect].description" + ], + "core/audits/font-display.js | title": ["audits[font-display].title"], + "core/audits/font-display.js | description": [ + "audits[font-display].description" + ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[font-display].details.headings[1].label", + "audits[unminified-javascript].details.headings[2].label", + "audits[unused-javascript].details.headings[2].label", + "audits[uses-text-compression].details.headings[2].label", + "audits[font-display-insight].details.headings[1].label", + "audits[image-delivery-insight].details.headings[2].label" + ], + "core/audits/network-rtt.js | title": ["audits[network-rtt].title"], + "core/audits/network-rtt.js | description": [ + "audits[network-rtt].description" + ], + "core/audits/network-server-latency.js | title": [ + "audits[network-server-latency].title" + ], + "core/audits/network-server-latency.js | description": [ + "audits[network-server-latency].description" + ], + "core/lib/i18n/i18n.js | columnResourceType": [ + "audits[resource-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnRequests": [ + "audits[resource-summary].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnTransferSize": [ + "audits[resource-summary].details.headings[2].label", + "audits[third-party-summary].details.headings[1].label", + "audits[uses-long-cache-ttl].details.headings[2].label", + "audits[total-byte-weight].details.headings[1].label", + "audits[unminified-javascript].details.headings[1].label", + "audits[unused-javascript].details.headings[1].label", + "audits[uses-text-compression].details.headings[1].label", + "audits[cache-insight].details.headings[2].label", + "audits[render-blocking-insight].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | total": [ + "audits[resource-summary].details.items[0].label", + "audits[cls-culprits-insight].details.items[0].items[0].node.value" + ], + "core/lib/i18n/i18n.js | scriptResourceType": [ + "audits[resource-summary].details.items[1].label" + ], + "core/lib/i18n/i18n.js | fontResourceType": [ + "audits[resource-summary].details.items[2].label" + ], + "core/lib/i18n/i18n.js | imageResourceType": [ + "audits[resource-summary].details.items[3].label" + ], + "core/lib/i18n/i18n.js | documentResourceType": [ + "audits[resource-summary].details.items[4].label" + ], + "core/lib/i18n/i18n.js | otherResourceType": [ + "audits[resource-summary].details.items[5].label" + ], + "core/lib/i18n/i18n.js | stylesheetResourceType": [ + "audits[resource-summary].details.items[6].label" + ], + "core/lib/i18n/i18n.js | mediaResourceType": [ + "audits[resource-summary].details.items[7].label" + ], + "core/lib/i18n/i18n.js | thirdPartyResourceType": [ + "audits[resource-summary].details.items[8].label" + ], + "core/audits/third-party-summary.js | title": [ + "audits[third-party-summary].title" + ], + "core/audits/third-party-summary.js | description": [ + "audits[third-party-summary].description" + ], + "core/audits/third-party-summary.js | displayValue": [ + { + "values": { + "timeInMs": 0 + }, + "path": "audits[third-party-summary].displayValue" + } + ], + "core/audits/third-party-summary.js | columnThirdParty": [ + "audits[third-party-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnBlockingTime": [ + "audits[third-party-summary].details.headings[2].label" + ], + "core/audits/third-party-facades.js | title": [ + "audits[third-party-facades].title" + ], + "core/audits/third-party-facades.js | description": [ + "audits[third-party-facades].description" + ], + "core/audits/largest-contentful-paint-element.js | title": [ + "audits[largest-contentful-paint-element].title" + ], + "core/audits/largest-contentful-paint-element.js | description": [ + "audits[largest-contentful-paint-element].description" + ], + "core/lib/i18n/i18n.js | columnElement": [ + "audits[largest-contentful-paint-element].details.items[0].headings[0].label", + "audits[layout-shifts].details.headings[0].label", + "audits[non-composited-animations].details.headings[0].label", + "audits[dom-size].details.headings[1].label", + "audits[cls-culprits-insight].details.items[0].headings[0].label", + "audits[dom-size-insight].details.headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPhase": [ + "audits[largest-contentful-paint-element].details.items[1].headings[0].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPercentOfLCP": [ + "audits[largest-contentful-paint-element].details.items[1].headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnTiming": [ + "audits[largest-contentful-paint-element].details.items[1].headings[2].label" + ], + "core/audits/largest-contentful-paint-element.js | itemTTFB": [ + "audits[largest-contentful-paint-element].details.items[1].items[0].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[1].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadTime": [ + "audits[largest-contentful-paint-element].details.items[1].items[2].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemRenderDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[3].phase" + ], + "core/audits/lcp-lazy-loaded.js | title": [ + "audits[lcp-lazy-loaded].title" + ], + "core/audits/lcp-lazy-loaded.js | description": [ + "audits[lcp-lazy-loaded].description" + ], + "core/audits/layout-shifts.js | title": ["audits[layout-shifts].title"], + "core/audits/layout-shifts.js | description": [ + "audits[layout-shifts].description" + ], + "core/audits/layout-shifts.js | displayValueShiftsFound": [ + { + "values": { + "shiftCount": 1 + }, + "path": "audits[layout-shifts].displayValue" + } + ], + "core/audits/layout-shifts.js | columnScore": [ + "audits[layout-shifts].details.headings[1].label" + ], + "core/audits/long-tasks.js | title": ["audits[long-tasks].title"], + "core/audits/long-tasks.js | description": [ + "audits[long-tasks].description" + ], + "core/audits/long-tasks.js | displayValue": [ + { + "values": { + "itemCount": 7 + }, + "path": "audits[long-tasks].displayValue" + } + ], + "core/audits/non-composited-animations.js | title": [ + "audits[non-composited-animations].title" + ], + "core/audits/non-composited-animations.js | description": [ + "audits[non-composited-animations].description" + ], + "core/audits/unsized-images.js | failureTitle": [ + "audits[unsized-images].title" + ], + "core/audits/unsized-images.js | description": [ + "audits[unsized-images].description" + ], + "core/audits/valid-source-maps.js | title": [ + "audits[valid-source-maps].title" + ], + "core/audits/valid-source-maps.js | description": [ + "audits[valid-source-maps].description" + ], + "core/audits/valid-source-maps.js | columnMapURL": [ + "audits[valid-source-maps].details.headings[1].label" + ], + "core/audits/prioritize-lcp-image.js | title": [ + "audits[prioritize-lcp-image].title" + ], + "core/audits/prioritize-lcp-image.js | description": [ + "audits[prioritize-lcp-image].description" + ], + "core/audits/csp-xss.js | title": ["audits[csp-xss].title"], + "core/audits/csp-xss.js | description": ["audits[csp-xss].description"], + "core/audits/csp-xss.js | columnDirective": [ + "audits[csp-xss].details.headings[1].label" + ], + "core/audits/csp-xss.js | columnSeverity": [ + "audits[csp-xss].details.headings[2].label" + ], + "core/lib/i18n/i18n.js | itemSeverityHigh": [ + "audits[csp-xss].details.items[0].severity", + "audits[has-hsts].details.items[0].severity", + "audits[origin-isolation].details.items[0].severity", + "audits[clickjacking-mitigation].details.items[0].severity", + "audits[trusted-types-xss].details.items[0].severity" + ], + "core/audits/csp-xss.js | noCsp": [ + "audits[csp-xss].details.items[0].description" + ], + "core/audits/has-hsts.js | title": ["audits[has-hsts].title"], + "core/audits/has-hsts.js | description": ["audits[has-hsts].description"], + "core/audits/has-hsts.js | columnDirective": [ + "audits[has-hsts].details.headings[1].label" + ], + "core/audits/has-hsts.js | columnSeverity": [ + "audits[has-hsts].details.headings[2].label" + ], + "core/audits/has-hsts.js | noHsts": [ + "audits[has-hsts].details.items[0].description" + ], + "core/audits/origin-isolation.js | title": [ + "audits[origin-isolation].title" + ], + "core/audits/origin-isolation.js | description": [ + "audits[origin-isolation].description" + ], + "core/audits/origin-isolation.js | columnDirective": [ + "audits[origin-isolation].details.headings[1].label" + ], + "core/audits/origin-isolation.js | columnSeverity": [ + "audits[origin-isolation].details.headings[2].label" + ], + "core/audits/origin-isolation.js | noCoop": [ + "audits[origin-isolation].details.items[0].description" + ], + "core/audits/clickjacking-mitigation.js | title": [ + "audits[clickjacking-mitigation].title" + ], + "core/audits/clickjacking-mitigation.js | description": [ + "audits[clickjacking-mitigation].description" + ], + "core/audits/clickjacking-mitigation.js | columnSeverity": [ + "audits[clickjacking-mitigation].details.headings[1].label" + ], + "core/audits/clickjacking-mitigation.js | noClickjackingMitigation": [ + "audits[clickjacking-mitigation].details.items[0].description" + ], + "core/audits/trusted-types-xss.js | title": [ + "audits[trusted-types-xss].title" + ], + "core/audits/trusted-types-xss.js | description": [ + "audits[trusted-types-xss].description" + ], + "core/audits/trusted-types-xss.js | columnSeverity": [ + "audits[trusted-types-xss].details.headings[1].label" + ], + "core/audits/trusted-types-xss.js | noTrustedTypesToMitigateXss": [ + "audits[trusted-types-xss].details.items[0].description" + ], + "core/audits/accessibility/accesskeys.js | title": [ + "audits.accesskeys.title" + ], + "core/audits/accessibility/accesskeys.js | description": [ + "audits.accesskeys.description" + ], + "core/audits/accessibility/aria-allowed-attr.js | title": [ + "audits[aria-allowed-attr].title" + ], + "core/audits/accessibility/aria-allowed-attr.js | description": [ + "audits[aria-allowed-attr].description" + ], + "core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[aria-allowed-attr].details.headings[0].label", + "audits[aria-allowed-role].details.headings[0].label", + "audits[aria-conditional-attr].details.headings[0].label", + "audits[aria-deprecated-role].details.headings[0].label", + "audits[aria-hidden-body].details.headings[0].label", + "audits[aria-hidden-focus].details.headings[0].label", + "audits[aria-prohibited-attr].details.headings[0].label", + "audits[aria-required-attr].details.headings[0].label", + "audits[aria-roles].details.headings[0].label", + "audits[aria-valid-attr-value].details.headings[0].label", + "audits[aria-valid-attr].details.headings[0].label", + "audits[button-name].details.headings[0].label", + "audits[color-contrast].details.headings[0].label", + "audits[document-title].details.headings[0].label", + "audits[heading-order].details.headings[0].label", + "audits[html-has-lang].details.headings[0].label", + "audits[html-lang-valid].details.headings[0].label", + "audits[image-alt].details.headings[0].label", + "audits[image-redundant-alt].details.headings[0].label", + "audits[label-content-name-mismatch].details.headings[0].label", + "audits.label.details.headings[0].label", + "audits[link-name].details.headings[0].label", + "audits.list.details.headings[0].label", + "audits.listitem.details.headings[0].label", + "audits[meta-viewport].details.headings[0].label", + "audits[skip-link].details.headings[0].label", + "audits[target-size].details.headings[0].label", + "audits[paste-preventing-inputs].details.headings[0].label" + ], + "core/audits/accessibility/aria-allowed-role.js | title": [ + "audits[aria-allowed-role].title" + ], + "core/audits/accessibility/aria-allowed-role.js | description": [ + "audits[aria-allowed-role].description" + ], + "core/audits/accessibility/aria-command-name.js | title": [ + "audits[aria-command-name].title" + ], + "core/audits/accessibility/aria-command-name.js | description": [ + "audits[aria-command-name].description" + ], + "core/audits/accessibility/aria-conditional-attr.js | title": [ + "audits[aria-conditional-attr].title" + ], + "core/audits/accessibility/aria-conditional-attr.js | description": [ + "audits[aria-conditional-attr].description" + ], + "core/audits/accessibility/aria-deprecated-role.js | title": [ + "audits[aria-deprecated-role].title" + ], + "core/audits/accessibility/aria-deprecated-role.js | description": [ + "audits[aria-deprecated-role].description" + ], + "core/audits/accessibility/aria-dialog-name.js | title": [ + "audits[aria-dialog-name].title" + ], + "core/audits/accessibility/aria-dialog-name.js | description": [ + "audits[aria-dialog-name].description" + ], + "core/audits/accessibility/aria-hidden-body.js | title": [ + "audits[aria-hidden-body].title" + ], + "core/audits/accessibility/aria-hidden-body.js | description": [ + "audits[aria-hidden-body].description" + ], + "core/audits/accessibility/aria-hidden-focus.js | title": [ + "audits[aria-hidden-focus].title" + ], + "core/audits/accessibility/aria-hidden-focus.js | description": [ + "audits[aria-hidden-focus].description" + ], + "core/audits/accessibility/aria-input-field-name.js | title": [ + "audits[aria-input-field-name].title" + ], + "core/audits/accessibility/aria-input-field-name.js | description": [ + "audits[aria-input-field-name].description" + ], + "core/audits/accessibility/aria-meter-name.js | title": [ + "audits[aria-meter-name].title" + ], + "core/audits/accessibility/aria-meter-name.js | description": [ + "audits[aria-meter-name].description" + ], + "core/audits/accessibility/aria-progressbar-name.js | title": [ + "audits[aria-progressbar-name].title" + ], + "core/audits/accessibility/aria-progressbar-name.js | description": [ + "audits[aria-progressbar-name].description" + ], + "core/audits/accessibility/aria-prohibited-attr.js | title": [ + "audits[aria-prohibited-attr].title" + ], + "core/audits/accessibility/aria-prohibited-attr.js | description": [ + "audits[aria-prohibited-attr].description" + ], + "core/audits/accessibility/aria-required-attr.js | title": [ + "audits[aria-required-attr].title" + ], + "core/audits/accessibility/aria-required-attr.js | description": [ + "audits[aria-required-attr].description" + ], + "core/audits/accessibility/aria-required-children.js | title": [ + "audits[aria-required-children].title" + ], + "core/audits/accessibility/aria-required-children.js | description": [ + "audits[aria-required-children].description" + ], + "core/audits/accessibility/aria-required-parent.js | title": [ + "audits[aria-required-parent].title" + ], + "core/audits/accessibility/aria-required-parent.js | description": [ + "audits[aria-required-parent].description" + ], + "core/audits/accessibility/aria-roles.js | title": [ + "audits[aria-roles].title" + ], + "core/audits/accessibility/aria-roles.js | description": [ + "audits[aria-roles].description" + ], + "core/audits/accessibility/aria-text.js | title": [ + "audits[aria-text].title" + ], + "core/audits/accessibility/aria-text.js | description": [ + "audits[aria-text].description" + ], + "core/audits/accessibility/aria-toggle-field-name.js | title": [ + "audits[aria-toggle-field-name].title" + ], + "core/audits/accessibility/aria-toggle-field-name.js | description": [ + "audits[aria-toggle-field-name].description" + ], + "core/audits/accessibility/aria-tooltip-name.js | title": [ + "audits[aria-tooltip-name].title" + ], + "core/audits/accessibility/aria-tooltip-name.js | description": [ + "audits[aria-tooltip-name].description" + ], + "core/audits/accessibility/aria-treeitem-name.js | title": [ + "audits[aria-treeitem-name].title" + ], + "core/audits/accessibility/aria-treeitem-name.js | description": [ + "audits[aria-treeitem-name].description" + ], + "core/audits/accessibility/aria-valid-attr-value.js | title": [ + "audits[aria-valid-attr-value].title" + ], + "core/audits/accessibility/aria-valid-attr-value.js | description": [ + "audits[aria-valid-attr-value].description" + ], + "core/audits/accessibility/aria-valid-attr.js | title": [ + "audits[aria-valid-attr].title" + ], + "core/audits/accessibility/aria-valid-attr.js | description": [ + "audits[aria-valid-attr].description" + ], + "core/audits/accessibility/button-name.js | title": [ + "audits[button-name].title" + ], + "core/audits/accessibility/button-name.js | description": [ + "audits[button-name].description" + ], + "core/audits/accessibility/bypass.js | title": ["audits.bypass.title"], + "core/audits/accessibility/bypass.js | description": [ + "audits.bypass.description" + ], + "core/audits/accessibility/color-contrast.js | title": [ + "audits[color-contrast].title" + ], + "core/audits/accessibility/color-contrast.js | description": [ + "audits[color-contrast].description" + ], + "core/audits/accessibility/definition-list.js | title": [ + "audits[definition-list].title" + ], + "core/audits/accessibility/definition-list.js | description": [ + "audits[definition-list].description" + ], + "core/audits/accessibility/dlitem.js | title": ["audits.dlitem.title"], + "core/audits/accessibility/dlitem.js | description": [ + "audits.dlitem.description" + ], + "core/audits/accessibility/document-title.js | title": [ + "audits[document-title].title" + ], + "core/audits/accessibility/document-title.js | description": [ + "audits[document-title].description" + ], + "core/audits/accessibility/duplicate-id-aria.js | title": [ + "audits[duplicate-id-aria].title" + ], + "core/audits/accessibility/duplicate-id-aria.js | description": [ + "audits[duplicate-id-aria].description" + ], + "core/audits/accessibility/empty-heading.js | title": [ + "audits[empty-heading].title" + ], + "core/audits/accessibility/empty-heading.js | description": [ + "audits[empty-heading].description" + ], + "core/audits/accessibility/form-field-multiple-labels.js | title": [ + "audits[form-field-multiple-labels].title" + ], + "core/audits/accessibility/form-field-multiple-labels.js | description": [ + "audits[form-field-multiple-labels].description" + ], + "core/audits/accessibility/frame-title.js | title": [ + "audits[frame-title].title" + ], + "core/audits/accessibility/frame-title.js | description": [ + "audits[frame-title].description" + ], + "core/audits/accessibility/heading-order.js | title": [ + "audits[heading-order].title" + ], + "core/audits/accessibility/heading-order.js | description": [ + "audits[heading-order].description" + ], + "core/audits/accessibility/html-has-lang.js | title": [ + "audits[html-has-lang].title" + ], + "core/audits/accessibility/html-has-lang.js | description": [ + "audits[html-has-lang].description" + ], + "core/audits/accessibility/html-lang-valid.js | title": [ + "audits[html-lang-valid].title" + ], + "core/audits/accessibility/html-lang-valid.js | description": [ + "audits[html-lang-valid].description" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | title": [ + "audits[html-xml-lang-mismatch].title" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | description": [ + "audits[html-xml-lang-mismatch].description" + ], + "core/audits/accessibility/identical-links-same-purpose.js | title": [ + "audits[identical-links-same-purpose].title" + ], + "core/audits/accessibility/identical-links-same-purpose.js | description": [ + "audits[identical-links-same-purpose].description" + ], + "core/audits/accessibility/image-alt.js | title": [ + "audits[image-alt].title" + ], + "core/audits/accessibility/image-alt.js | description": [ + "audits[image-alt].description" + ], + "core/audits/accessibility/image-redundant-alt.js | title": [ + "audits[image-redundant-alt].title" + ], + "core/audits/accessibility/image-redundant-alt.js | description": [ + "audits[image-redundant-alt].description" + ], + "core/audits/accessibility/input-button-name.js | title": [ + "audits[input-button-name].title" + ], + "core/audits/accessibility/input-button-name.js | description": [ + "audits[input-button-name].description" + ], + "core/audits/accessibility/input-image-alt.js | title": [ + "audits[input-image-alt].title" + ], + "core/audits/accessibility/input-image-alt.js | description": [ + "audits[input-image-alt].description" + ], + "core/audits/accessibility/label-content-name-mismatch.js | title": [ + "audits[label-content-name-mismatch].title" + ], + "core/audits/accessibility/label-content-name-mismatch.js | description": [ + "audits[label-content-name-mismatch].description" + ], + "core/audits/accessibility/label.js | title": ["audits.label.title"], + "core/audits/accessibility/label.js | description": [ + "audits.label.description" + ], + "core/audits/accessibility/landmark-one-main.js | title": [ + "audits[landmark-one-main].title" + ], + "core/audits/accessibility/landmark-one-main.js | description": [ + "audits[landmark-one-main].description" + ], + "core/audits/accessibility/link-name.js | title": [ + "audits[link-name].title" + ], + "core/audits/accessibility/link-name.js | description": [ + "audits[link-name].description" + ], + "core/audits/accessibility/link-in-text-block.js | title": [ + "audits[link-in-text-block].title" + ], + "core/audits/accessibility/link-in-text-block.js | description": [ + "audits[link-in-text-block].description" + ], + "core/audits/accessibility/list.js | title": ["audits.list.title"], + "core/audits/accessibility/list.js | description": [ + "audits.list.description" + ], + "core/audits/accessibility/listitem.js | title": [ + "audits.listitem.title" + ], + "core/audits/accessibility/listitem.js | description": [ + "audits.listitem.description" + ], + "core/audits/accessibility/meta-refresh.js | title": [ + "audits[meta-refresh].title" + ], + "core/audits/accessibility/meta-refresh.js | description": [ + "audits[meta-refresh].description" + ], + "core/audits/accessibility/meta-viewport.js | title": [ + "audits[meta-viewport].title" + ], + "core/audits/accessibility/meta-viewport.js | description": [ + "audits[meta-viewport].description" + ], + "core/audits/accessibility/object-alt.js | title": [ + "audits[object-alt].title" + ], + "core/audits/accessibility/object-alt.js | description": [ + "audits[object-alt].description" + ], + "core/audits/accessibility/select-name.js | title": [ + "audits[select-name].title" + ], + "core/audits/accessibility/select-name.js | description": [ + "audits[select-name].description" + ], + "core/audits/accessibility/skip-link.js | title": [ + "audits[skip-link].title" + ], + "core/audits/accessibility/skip-link.js | description": [ + "audits[skip-link].description" + ], + "core/audits/accessibility/tabindex.js | title": [ + "audits.tabindex.title" + ], + "core/audits/accessibility/tabindex.js | description": [ + "audits.tabindex.description" + ], + "core/audits/accessibility/table-duplicate-name.js | title": [ + "audits[table-duplicate-name].title" + ], + "core/audits/accessibility/table-duplicate-name.js | description": [ + "audits[table-duplicate-name].description" + ], + "core/audits/accessibility/table-fake-caption.js | title": [ + "audits[table-fake-caption].title" + ], + "core/audits/accessibility/table-fake-caption.js | description": [ + "audits[table-fake-caption].description" + ], + "core/audits/accessibility/target-size.js | title": [ + "audits[target-size].title" + ], + "core/audits/accessibility/target-size.js | description": [ + "audits[target-size].description" + ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], + "core/audits/accessibility/td-headers-attr.js | title": [ + "audits[td-headers-attr].title" + ], + "core/audits/accessibility/td-headers-attr.js | description": [ + "audits[td-headers-attr].description" + ], + "core/audits/accessibility/th-has-data-cells.js | title": [ + "audits[th-has-data-cells].title" + ], + "core/audits/accessibility/th-has-data-cells.js | description": [ + "audits[th-has-data-cells].description" + ], + "core/audits/accessibility/valid-lang.js | title": [ + "audits[valid-lang].title" + ], + "core/audits/accessibility/valid-lang.js | description": [ + "audits[valid-lang].description" + ], + "core/audits/accessibility/video-caption.js | title": [ + "audits[video-caption].title" + ], + "core/audits/accessibility/video-caption.js | description": [ + "audits[video-caption].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | title": [ + "audits[uses-long-cache-ttl].title" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | description": [ + "audits[uses-long-cache-ttl].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | displayValue": [ + { + "values": { + "itemCount": 0 + }, + "path": "audits[uses-long-cache-ttl].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnCacheTTL": [ + "audits[uses-long-cache-ttl].details.headings[1].label", + "audits[cache-insight].details.headings[1].label" + ], + "core/audits/byte-efficiency/total-byte-weight.js | failureTitle": [ + "audits[total-byte-weight].title" + ], + "core/audits/byte-efficiency/total-byte-weight.js | description": [ + "audits[total-byte-weight].description" + ], + "core/audits/byte-efficiency/total-byte-weight.js | displayValue": [ + { + "values": { + "totalBytes": 2930513 + }, + "path": "audits[total-byte-weight].displayValue" + } + ], + "core/audits/byte-efficiency/offscreen-images.js | title": [ + "audits[offscreen-images].title" + ], + "core/audits/byte-efficiency/offscreen-images.js | description": [ + "audits[offscreen-images].description" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | title": [ + "audits[render-blocking-resources].title" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | description": [ + "audits[render-blocking-resources].description" + ], + "core/audits/byte-efficiency/unminified-css.js | title": [ + "audits[unminified-css].title" + ], + "core/audits/byte-efficiency/unminified-css.js | description": [ + "audits[unminified-css].description" + ], + "core/audits/byte-efficiency/unminified-javascript.js | title": [ + "audits[unminified-javascript].title" + ], + "core/audits/byte-efficiency/unminified-javascript.js | description": [ + "audits[unminified-javascript].description" + ], + "core/lib/i18n/i18n.js | displayValueByteSavings": [ + { + "values": { + "wastedBytes": 1000494 + }, + "path": "audits[unminified-javascript].displayValue" + }, + { + "values": { + "wastedBytes": 795305 + }, + "path": "audits[unused-javascript].displayValue" + }, + { + "values": { + "wastedBytes": 1892425 + }, + "path": "audits[uses-text-compression].displayValue" + }, + { + "values": { + "wastedBytes": 1561 + }, + "path": "audits[document-latency-insight].displayValue" + } + ], + "core/audits/byte-efficiency/unused-css-rules.js | title": [ + "audits[unused-css-rules].title" + ], + "core/audits/byte-efficiency/unused-css-rules.js | description": [ + "audits[unused-css-rules].description" + ], + "core/audits/byte-efficiency/unused-javascript.js | title": [ + "audits[unused-javascript].title" + ], + "core/audits/byte-efficiency/unused-javascript.js | description": [ + "audits[unused-javascript].description" + ], + "core/audits/byte-efficiency/modern-image-formats.js | title": [ + "audits[modern-image-formats].title" + ], + "core/audits/byte-efficiency/modern-image-formats.js | description": [ + "audits[modern-image-formats].description" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | title": [ + "audits[uses-optimized-images].title" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | description": [ + "audits[uses-optimized-images].description" + ], + "core/audits/byte-efficiency/uses-text-compression.js | title": [ + "audits[uses-text-compression].title" + ], + "core/audits/byte-efficiency/uses-text-compression.js | description": [ + "audits[uses-text-compression].description" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | title": [ + "audits[uses-responsive-images].title" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | description": [ + "audits[uses-responsive-images].description" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | title": [ + "audits[efficient-animated-content].title" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | description": [ + "audits[efficient-animated-content].description" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | title": [ + "audits[duplicated-javascript].title" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | description": [ + "audits[duplicated-javascript].description" + ], + "core/audits/byte-efficiency/legacy-javascript.js | title": [ + "audits[legacy-javascript].title" + ], + "core/audits/byte-efficiency/legacy-javascript.js | description": [ + "audits[legacy-javascript].description" + ], + "core/audits/dobetterweb/doctype.js | title": ["audits.doctype.title"], + "core/audits/dobetterweb/doctype.js | description": [ + "audits.doctype.description" + ], + "core/audits/dobetterweb/charset.js | title": ["audits.charset.title"], + "core/audits/dobetterweb/charset.js | description": [ + "audits.charset.description" + ], + "core/audits/dobetterweb/dom-size.js | title": ["audits[dom-size].title"], + "core/audits/dobetterweb/dom-size.js | description": [ + "audits[dom-size].description" + ], + "core/audits/dobetterweb/dom-size.js | displayValue": [ + { + "values": { + "itemCount": 101 + }, + "path": "audits[dom-size].displayValue" + } + ], + "core/audits/dobetterweb/dom-size.js | columnStatistic": [ + "audits[dom-size].details.headings[0].label" + ], + "core/audits/dobetterweb/dom-size.js | columnValue": [ + "audits[dom-size].details.headings[2].label" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMElements": [ + "audits[dom-size].details.items[0].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMDepth": [ + "audits[dom-size].details.items[1].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMWidth": [ + "audits[dom-size].details.items[2].statistic" + ], + "core/audits/dobetterweb/geolocation-on-start.js | title": [ + "audits[geolocation-on-start].title" + ], + "core/audits/dobetterweb/geolocation-on-start.js | description": [ + "audits[geolocation-on-start].description" + ], + "core/audits/dobetterweb/inspector-issues.js | title": [ + "audits[inspector-issues].title" + ], + "core/audits/dobetterweb/inspector-issues.js | description": [ + "audits[inspector-issues].description" + ], + "core/audits/dobetterweb/inspector-issues.js | columnIssueType": [ + "audits[inspector-issues].details.headings[0].label" + ], + "core/audits/dobetterweb/no-document-write.js | title": [ + "audits[no-document-write].title" + ], + "core/audits/dobetterweb/no-document-write.js | description": [ + "audits[no-document-write].description" + ], + "core/audits/dobetterweb/js-libraries.js | title": [ + "audits[js-libraries].title" + ], + "core/audits/dobetterweb/js-libraries.js | description": [ + "audits[js-libraries].description" + ], + "core/audits/dobetterweb/notification-on-start.js | title": [ + "audits[notification-on-start].title" + ], + "core/audits/dobetterweb/notification-on-start.js | description": [ + "audits[notification-on-start].description" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | title": [ + "audits[paste-preventing-inputs].title" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | description": [ + "audits[paste-preventing-inputs].description" + ], + "core/audits/dobetterweb/uses-http2.js | title": [ + "audits[uses-http2].title" + ], + "core/audits/dobetterweb/uses-http2.js | description": [ + "audits[uses-http2].description" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | title": [ + "audits[uses-passive-event-listeners].title" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | description": [ + "audits[uses-passive-event-listeners].description" + ], + "core/audits/seo/meta-description.js | title": [ + "audits[meta-description].title" + ], + "core/audits/seo/meta-description.js | description": [ + "audits[meta-description].description" + ], + "core/audits/seo/http-status-code.js | title": [ + "audits[http-status-code].title" + ], + "core/audits/seo/http-status-code.js | description": [ + "audits[http-status-code].description" + ], + "core/audits/seo/font-size.js | title": ["audits[font-size].title"], + "core/audits/seo/font-size.js | description": [ + "audits[font-size].description" + ], + "core/audits/seo/font-size.js | displayValue": [ + { + "values": { + "decimalProportion": 1 + }, + "path": "audits[font-size].displayValue" + } + ], + "core/audits/seo/font-size.js | columnSelector": [ + "audits[font-size].details.headings[1].label" + ], + "core/audits/seo/font-size.js | columnPercentPageText": [ + "audits[font-size].details.headings[2].label" + ], + "core/audits/seo/font-size.js | columnFontSize": [ + "audits[font-size].details.headings[3].label" + ], + "core/audits/seo/font-size.js | legibleText": [ + "audits[font-size].details.items[0].source.value" + ], + "core/audits/seo/link-text.js | title": ["audits[link-text].title"], + "core/audits/seo/link-text.js | description": [ + "audits[link-text].description" + ], + "core/audits/seo/crawlable-anchors.js | title": [ + "audits[crawlable-anchors].title" + ], + "core/audits/seo/crawlable-anchors.js | description": [ + "audits[crawlable-anchors].description" + ], + "core/audits/seo/crawlable-anchors.js | columnFailingLink": [ + "audits[crawlable-anchors].details.headings[0].label" + ], + "core/audits/seo/is-crawlable.js | title": ["audits[is-crawlable].title"], + "core/audits/seo/is-crawlable.js | description": [ + "audits[is-crawlable].description" + ], + "core/audits/seo/robots-txt.js | failureTitle": [ + "audits[robots-txt].title" + ], + "core/audits/seo/robots-txt.js | description": [ + "audits[robots-txt].description" + ], + "core/audits/seo/robots-txt.js | displayValueValidationError": [ + { + "values": { + "itemCount": 52 + }, + "path": "audits[robots-txt].displayValue" + } + ], + "core/audits/seo/hreflang.js | title": ["audits.hreflang.title"], + "core/audits/seo/hreflang.js | description": [ + "audits.hreflang.description" + ], + "core/audits/seo/canonical.js | title": ["audits.canonical.title"], + "core/audits/seo/canonical.js | description": [ + "audits.canonical.description" + ], + "core/audits/seo/manual/structured-data.js | title": [ + "audits[structured-data].title" + ], + "core/audits/seo/manual/structured-data.js | description": [ + "audits[structured-data].description" + ], + "core/audits/bf-cache.js | failureTitle": ["audits[bf-cache].title"], + "core/audits/bf-cache.js | description": ["audits[bf-cache].description"], + "core/audits/bf-cache.js | displayValue": [ + { + "values": { + "itemCount": 1 + }, + "path": "audits[bf-cache].displayValue" + } + ], + "core/audits/bf-cache.js | failureReasonColumn": [ + "audits[bf-cache].details.headings[0].label" + ], + "core/audits/bf-cache.js | failureTypeColumn": [ + "audits[bf-cache].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/panels/application/components/BackForwardCacheStrings.js | webSocket": [ + "audits[bf-cache].details.items[0].reason" + ], + "core/audits/bf-cache.js | supportPendingFailureType": [ + "audits[bf-cache].details.items[0].failureType" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | title": [ + "audits[cache-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | description": [ + "audits[cache-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | requestColumn": [ + "audits[cache-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "core/audits/insights/cls-culprits-insight.js | columnScore": [ + "audits[cls-culprits-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": [ + "audits[document-latency-insight].details.items.noRedirects.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": [ + { + "values": { + "PH1": "5 ms" + }, + "path": "audits[document-latency-insight].details.items.serverResponseIsFast.label" + } + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | failedTextCompression": [ + "audits[document-latency-insight].details.items.usesCompression.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | title": [ + "audits[duplicated-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | description": [ + "audits[duplicated-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnSource": [ + "audits[duplicated-javascript-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnDuplicatedBytes": [ + "audits[duplicated-javascript-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | totalReflowTime": [ + "audits[forced-reflow-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "core/lib/i18n/i18n.js | columnResourceSize": [ + "audits[image-delivery-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | title": [ + "audits[inp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | description": [ + "audits[inp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | title": [ + "audits[lcp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | description": [ + "audits[lcp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | subpart": [ + "audits[lcp-breakdown-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | timeToFirstByte": [ + "audits[lcp-breakdown-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | elementRenderDelay": [ + "audits[lcp-breakdown-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | title": [ + "audits[legacy-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | description": [ + "audits[legacy-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | columnWastedBytes": [ + "audits[legacy-javascript-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | title": [ + "audits[modern-http-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | description": [ + "audits[modern-http-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | protocol": [ + "audits[modern-http-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | title": [ + "audits[network-dependency-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | description": [ + "audits[network-dependency-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableTitle": [ + "audits[network-dependency-tree-insight].details.items[1].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableDescription": [ + "audits[network-dependency-tree-insight].details.items[1].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectOrigins": [ + "audits[network-dependency-tree-insight].details.items[1].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableTitle": [ + "audits[network-dependency-tree-insight].details.items[2].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableDescription": [ + "audits[network-dependency-tree-insight].details.items[2].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectCandidates": [ + "audits[network-dependency-tree-insight].details.items[2].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": [ + "audits[third-parties-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": [ + "audits[third-parties-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnMainThreadTime": [ + "audits[third-parties-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], + "core/config/default-config.js | performanceCategoryTitle": [ + "categories.performance.title" + ], + "core/config/default-config.js | a11yCategoryTitle": [ + "categories.accessibility.title" + ], + "core/config/default-config.js | a11yCategoryDescription": [ + "categories.accessibility.description" + ], + "core/config/default-config.js | a11yCategoryManualDescription": [ + "categories.accessibility.manualDescription" + ], + "core/config/default-config.js | bestPracticesCategoryTitle": [ + "categories[best-practices].title" + ], + "core/config/default-config.js | seoCategoryTitle": [ + "categories.seo.title" + ], + "core/config/default-config.js | seoCategoryDescription": [ + "categories.seo.description" + ], + "core/config/default-config.js | seoCategoryManualDescription": [ + "categories.seo.manualDescription" + ], + "core/config/default-config.js | metricGroupTitle": [ + "categoryGroups.metrics.title" + ], + "core/config/default-config.js | insightsGroupTitle": [ + "categoryGroups.insights.title" + ], + "core/config/default-config.js | insightsGroupDescription": [ + "categoryGroups.insights.description" + ], + "core/config/default-config.js | diagnosticsGroupTitle": [ + "categoryGroups.diagnostics.title" + ], + "core/config/default-config.js | diagnosticsGroupDescription": [ + "categoryGroups.diagnostics.description" + ], + "core/config/default-config.js | a11yBestPracticesGroupTitle": [ + "categoryGroups[a11y-best-practices].title" + ], + "core/config/default-config.js | a11yBestPracticesGroupDescription": [ + "categoryGroups[a11y-best-practices].description" + ], + "core/config/default-config.js | a11yColorContrastGroupTitle": [ + "categoryGroups[a11y-color-contrast].title" + ], + "core/config/default-config.js | a11yColorContrastGroupDescription": [ + "categoryGroups[a11y-color-contrast].description" + ], + "core/config/default-config.js | a11yNamesLabelsGroupTitle": [ + "categoryGroups[a11y-names-labels].title" + ], + "core/config/default-config.js | a11yNamesLabelsGroupDescription": [ + "categoryGroups[a11y-names-labels].description" + ], + "core/config/default-config.js | a11yNavigationGroupTitle": [ + "categoryGroups[a11y-navigation].title" + ], + "core/config/default-config.js | a11yNavigationGroupDescription": [ + "categoryGroups[a11y-navigation].description" + ], + "core/config/default-config.js | a11yAriaGroupTitle": [ + "categoryGroups[a11y-aria].title" + ], + "core/config/default-config.js | a11yAriaGroupDescription": [ + "categoryGroups[a11y-aria].description" + ], + "core/config/default-config.js | a11yLanguageGroupTitle": [ + "categoryGroups[a11y-language].title" + ], + "core/config/default-config.js | a11yLanguageGroupDescription": [ + "categoryGroups[a11y-language].description" + ], + "core/config/default-config.js | a11yAudioVideoGroupTitle": [ + "categoryGroups[a11y-audio-video].title" + ], + "core/config/default-config.js | a11yAudioVideoGroupDescription": [ + "categoryGroups[a11y-audio-video].description" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupTitle": [ + "categoryGroups[a11y-tables-lists].title" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupDescription": [ + "categoryGroups[a11y-tables-lists].description" + ], + "core/config/default-config.js | seoMobileGroupTitle": [ + "categoryGroups[seo-mobile].title" + ], + "core/config/default-config.js | seoMobileGroupDescription": [ + "categoryGroups[seo-mobile].description" + ], + "core/config/default-config.js | seoContentGroupTitle": [ + "categoryGroups[seo-content].title" + ], + "core/config/default-config.js | seoContentGroupDescription": [ + "categoryGroups[seo-content].description" + ], + "core/config/default-config.js | seoCrawlingGroupTitle": [ + "categoryGroups[seo-crawl].title" + ], + "core/config/default-config.js | seoCrawlingGroupDescription": [ + "categoryGroups[seo-crawl].description" + ], + "core/config/default-config.js | bestPracticesTrustSafetyGroupTitle": [ + "categoryGroups[best-practices-trust-safety].title" + ], + "core/config/default-config.js | bestPracticesUXGroupTitle": [ + "categoryGroups[best-practices-ux].title" + ], + "core/config/default-config.js | bestPracticesBrowserCompatGroupTitle": [ + "categoryGroups[best-practices-browser-compat].title" + ], + "core/config/default-config.js | bestPracticesGeneralGroupTitle": [ + "categoryGroups[best-practices-general].title" + ] + } + } +} diff --git a/docs/performance/lighthouse-baseline-pre-busca-prod-desktop.json b/docs/performance/lighthouse-baseline-pre-busca-prod-desktop.json new file mode 100644 index 00000000..9fee7a3a --- /dev/null +++ b/docs/performance/lighthouse-baseline-pre-busca-prod-desktop.json @@ -0,0 +1,10331 @@ +{ + "lighthouseVersion": "12.8.2", + "requestedUrl": "http://localhost:4173/", + "mainDocumentUrl": "http://localhost:4173/", + "finalDisplayedUrl": "http://localhost:4173/", + "finalUrl": "http://localhost:4173/", + "fetchTime": "2026-06-04T03:43:00.663Z", + "gatherMode": "navigation", + "runWarnings": [], + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "environment": { + "networkUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", + "hostUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "benchmarkIndex": 764.5, + "credits": { + "axe-core": "4.12.0" + } + }, + "audits": { + "is-on-https": { + "id": "is-on-https", + "title": "Uses HTTPS", + "description": "All sites should be protected with HTTPS, even ones that don't handle sensitive data. This includes avoiding [mixed content](https://developers.google.com/web/fundamentals/security/prevent-mixed-content/what-is-mixed-content), where some resources are loaded over HTTP despite the initial request being served over HTTPS. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more about HTTPS](https://developer.chrome.com/docs/lighthouse/pwa/is-on-https/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Insecure URL" + }, + { + "key": "resolution", + "valueType": "text", + "label": "Request Resolution" + } + ], + "items": [] + } + }, + "redirects-http": { + "id": "redirects-http", + "title": "Redirects HTTP traffic to HTTPS", + "description": "Make sure that you redirect all HTTP traffic to HTTPS in order to enable secure web features for all your users. [Learn more](https://developer.chrome.com/docs/lighthouse/pwa/redirects-http/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "viewport": { + "id": "viewport", + "title": "Has a `` tag with `width` or `initial-scale`", + "description": "A `` not only optimizes your app for mobile screen sizes, but also prevents [a 300 millisecond delay to user input](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/). [Learn more about using the viewport meta tag](https://developer.chrome.com/docs/lighthouse/pwa/viewport/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "debugdata", + "viewportContent": "width=device-width, initial-scale=1.0" + }, + "guidanceLevel": 3 + }, + "first-contentful-paint": { + "id": "first-contentful-paint", + "title": "First Contentful Paint", + "description": "First Contentful Paint marks the time at which the first text or image is painted. [Learn more about the First Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint/).", + "score": 0.98, + "scoreDisplayMode": "numeric", + "numericValue": 648.1821, + "numericUnit": "millisecond", + "displayValue": "0.6 s", + "scoringOptions": { + "p10": 934, + "median": 1600 + } + }, + "largest-contentful-paint": { + "id": "largest-contentful-paint", + "title": "Largest Contentful Paint", + "description": "Largest Contentful Paint marks the time at which the largest text or image is painted. [Learn more about the Largest Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 0.98, + "scoreDisplayMode": "numeric", + "numericValue": 748.77315, + "numericUnit": "millisecond", + "displayValue": "0.7 s", + "scoringOptions": { + "p10": 1200, + "median": 2400 + } + }, + "first-meaningful-paint": { + "id": "first-meaningful-paint", + "title": "First Meaningful Paint", + "description": "First Meaningful Paint measures when the primary content of a page is visible. [Learn more about the First Meaningful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "speed-index": { + "id": "speed-index", + "title": "Speed Index", + "description": "Speed Index shows how quickly the contents of a page are visibly populated. [Learn more about the Speed Index metric](https://developer.chrome.com/docs/lighthouse/performance/speed-index/).", + "score": 1, + "scoreDisplayMode": "numeric", + "numericValue": 648.1821, + "numericUnit": "millisecond", + "displayValue": "0.6 s", + "scoringOptions": { + "p10": 1311, + "median": 2300 + } + }, + "screenshot-thumbnails": { + "id": "screenshot-thumbnails", + "title": "Screenshot Thumbnails", + "description": "This is what the load of your site looked like.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "filmstrip", + "scale": 3000, + "items": [ + { + "timing": 375, + "timestamp": 27710747153, + "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACMAPoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCoAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//Z" + }, + { + "timing": 750, + "timestamp": 27711122153, + "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACMAPoDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAQFAgMGCQEHCP/EAD4QAAIBAwMDAQYCCQEIAwEAAAECAwAEEQUSIQYTMUEUIlFSYaEycQcVIzZCdYGRsxYYJFZilLHS8TNDwcL/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIEBQP/xAAiEQEAAwACAgICAwAAAAAAAAAAAREhAjEEEgMiQVEUUmH/2gAMAwEAAhEDEQA/AP6ppSlApSlBgkqPJIi53RkBsggcjPn1pDNHMrNE4YKxQkehBwR/Qivzq+6e1m7utYmFvIZJrW8gt3kuFJV2KCFhzwAEL5/hLtgVstek76O4ZXtxHYub0vHbsiuzzy70fJ4yi5XPkFjjIrfrH7S36JmmRnGRn4V+K9XdAaxqWl9P2djYQ99bq6N009wRGI2WXtbyhyQN4GAPI8YrdH+j3qu31nSZDq4uYbS3ijecudxKIFYYPqSDz9ea16cf7Fv2JnVcbmAycDJ8n4VlX4l+ib9HutaLPpt11AixKyRM9qjtJiWNZMSSkkgPl1A25GF9K/baxz4xxmomyGiS5COVC5xWHtf/ACfeqnqLTpNRVI47mS1ZJe5vTOfwkY4Ix+Lz9K55eldRWOFTr87PG27eyNljhgc4ced3ke9x+IcY1HGJjUt3kMwlyMYIrbVfpETwwRxySNM6RqrSMOXIHk/U1X3nT88urT31vqc9v3fMajIHuoMjkc+5n68Z4BBxMaroKVzidPXaRCNNWmGEChirFgQuN34vP18/X0q00eyuLGCRLq9e8dn3B3GCBgceT8PvUE+lVmt6U2piApdSW7xMSGQZznGQR68ZH9c+lVo6e1ERBDr92SFxuI8tnz5+HGP61R0tK5p+m7tpGYa1cqPd2AAgJggnADYPj1z59at9IsprGBkuLuW7diDvkzwAAMYyfgT/AFqCdSoGsae9+luI5+w0UokDbNx4+AzjP1Oaqk6dvlMh/XdyC+4kqGHJXaPLHxx/+k8YDpKVqtImhtYYpJGldECtI3lyB5P51topSlKBSlKBSlKBSlKDV3T8v3p3T8v3rXSqjZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2GTPlPvXzuD5B/esKUGwS48L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oFKUoFQ9U1G20u2FxeuY4S4TdtJAJOBnHgfU1MrTdW0F3GI7qGKaMENtkUMMjwcH1oKVur9GWWONriQPJOtuuYXGWJIB8eCQRnxxWWldWaRqtxbw2Fw8sk5kCfs2A9wKWySOPxL/f86sTpWnltxsbUt3e/nsrnufP4/F9fNZJptjHLFKllbLLFntuIlBTIAODjjIAH9K19RB/1Npfs2oTmdxFY8zkxsMDcy5GRyMqwyPgaiJ1toUhmEd1I7RJG7KsDlsSIHXAxn8Jz/f4VeJY2iCYJbQKJm3y4jA3t8W+J+prCfTrG47vfs7aXu47m+JW34xjORzjA/sKYK9ep9Na9W0SSV53LBVWJju2orkjjxtdf71npnUem6lPBDaySmSaITIGiZQVIDDkjGdpBx5wam/qyxEyyiytu6rmRX7S7g54LA48nAyfpWcVlaxTLNFbQJMsYiV1jAYIPCg/D6eKYJFKUrIUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgV+TfpI/TfonRuryaVDaT6pqEOO8sThI4if4Sxz730Ar9Zrz367dpOt+oXdizNqFwST6/tGro8f4o+SZ9kmX75/tO2/wDwtL/1w/8ACn+07b/8LS/9cP8Awr+Z6Zrs/jfH+mbl6SUpSvLbKpur9Xk0TQZryCNJLgyRQQq/4e5JIsalsegLAn6Crmoesaba6xps9hfoXt5l2sASCOcggjkEEAg+hFBz+o3mpdPXukPdX51C1vrpLOZHhRDE7g7WTaB7u4YIbJwc54529U63Np2saXbwSlITHNd3m2Pe3YjAHHwJd0H5bvzEmLp4veWU+p6ld6j7E3ct0mWNVV8Fd52KNzAEgenOcZ5rDVOmItSvNUnububF/ZrYlVAHbiBYsFOPLbjn+nwrUV+UQ5uubE2E9xZWl7cSItuyRGLtmTvsVjwW+LAj7+Oa33/W2kWPtHeNwTbxSSyduIvgRkBxx6qWAJ8ZBGeDjGfo6CW4nl9vvIxLeRXexNgC9tNioPdyFAAPxBGRg18g6MtYrG9svbLlrS6nM7REIBhpe66khcsGOVy2TtOBWvoMLrrvS7V5VmttUVo4ZLg5snGY027mHHIAcHNTJOrtKWQqHmkTc8ayJEWV5FQuyL8WCg/TgjOQRVdNo8mvdR6y18bqDT+zFYiLt7RcRgl3wxGdrFtpx5C/UGoXUvSy2mjXCWDXs8krTRW8aIGFt7TIe9KMDJKq74J8DgeeVcR0Wg9UafrbSC1FxHst4rv9vEYwYpN21hnyPdYf0qF0t1Raa9ql/wCx363EQijlt4FQD9kcjfnPJLA8HGABxzk2em6Hb2ntbyO9xNdIsUjuAP2aghUAAACjLHHxY1h01obaFZraLqF1d20SCKBJ1jzEg4A3KoLYGBk5PFYmrw1Fi6w02SG3l7d2qTQXE6/stxxA22QYBJyD6DzUI/pD0YRySNDqaxxxR3DObKQAQvnbLnH4ODz9PFfIOho4LgOmr35ijW6SGFliKRrcHc4/BuODyMn0H1z9m6Hilsbm1bULgRz6ZFpTEIuRHGWww4/F7zfT6Uw1dazr1tpNzZ280V1NcXhcQpBCX3FV3EZ8A4B8n0qth640idrRLdb+WS5R3VEtJCU2SCOQPx7pVjznx/atXVdne3Gu9LG1e5T2e4leW4ihDhAYWUbsggAlgKkad0ja2F/bXMVxM4ihuInSQKe8Z5BJI7EAcllB4wByMUEPWestOOiXMsV3e2AktHu7a7W13l4lxmSNSCGA3KcEDg5xjmt9311otrd3Np33nuYBIpSMAl3jQuyDn8W0HzgZBGcjFVWv9FbOlprOzmur2aKwk03T45doECSBVySAM4AXJPOFPknm8t+lY4J9REV/dLY3xkeWzxGUDyDDlWK7hkknGcZ/tSf8GMfWFkbKK4ltb2N2tReyQiMO8MJ/jbaTxwcAZJwcDg10NvNHc28c8DrJDIodHU5DKRkEVy0HRawxBV1e/wC61kNPll2xAyQrnaMbMArubBHzHOeMdPZWsNlZwWtsgSCCNYo0H8KqMAf2FJoi26lKVFKUpQKUpQKUpQK89et/301/+YXH+Rq9Cq89Ot/301/+YXH+Rq7PD7lnkpTSlfM13svSalKV4j6FU3WN4un9NX109xd2yRJuaa0VWlQZGSoYEf39KuaruodJi1zRbvTbiaaGG5Ttu8O3cB64yCPtQVU3WNlBqUlm9pqGIr2Owln7Q7ccrhCmTnOG7ijIB5POKrNW6q361oEtm91FpD3lxFcXJEfYmWOCYt67+GjyDgA4PJ4qyn6Qhn9tL6jfZu72C/cjt8SQ7NoHueP2aZH0+taE6EsVEMJvr9rCC4kuIrJmjMSdxZFdAdm7aRK/BbjPGKqNdv8ApD0i5g7ltDdzEyQRqsYRy3eYrGchsDJGCCQRxkDNfZ/0haRbK8d1FdW99HK8L2U3bSVSqq5OS+wja6EYY53ADJ4qL1D01eW3Tdnp+n3uq33bvrR42Ywl7eKOVWJBKgNhV/i3E4HmrJ+jLX2pb6HUNQg1butK9+hj7sm5VQqwKFNu2NBjbxtB880w1rn680yMsyW2oSwItvJJOkHuRxzfgc5IOPjgEj4VG6h6lke/0qPSva1txqy2dxcqsfZcjcHjOTu4IxkDGRjNWGo9IQX51QzahfD9YxwxS7e3wIiSuMp5OTnOfPpWodE2q3G5NS1Fbf239YC1DRmNZslmIym7DEsSM45OMUyzaR4f0jaHNb3E8JuJYYkWUNEqvvjZwm4BWJHLDhgDg5xW2breJSscWkak917etg9swjR1ZozIrcvtKlRxz+eKzHRcC6FLoy6pqQ047VhhLRn2dFYMFQlMkcAe8WOBitl30fBc3d1dHUb+O4muortZI+3mKSNNg2goRgrwQc/0phrPTOstK1HXDpVs7m43SRhsqQXjOHXAYsCCDyQAcHBNZXvVlnbXTRrbXk8KXcdhJcRKpjSdyoCHLAnl1yQCBnGcg1v0Xp9NIu55LW/vTayyPN7G5QxLI53Ow93cMsScbsZJwBXP9S9Ly27+0aO+oSrc6ta309khjMQKzI0kg3DcOEzgNjPpzT8m0sl640xbaC5uYrq2t5IrqVpJVXEYt2KyBtrE5yOMZzW3T+pLXqDStUOmSzQT20ZBbMbMhK5VgQWQ/fkYIqEegNOkeRbi91Ce0b2kC1d07aLcEmRQQgbBJyMnI45q+07SntNOktLjUr2+DqUEtyU3quMYG1VB/Mgn41J6WO3JaP17Fa9N6fJrVpqPtZ0uO+LlI8XKhU7joQ2OC4JBwcHgVdX/AFlYWNzc280Fz34bpLQJ+zHddou4NpLAcqD5IOeMZrQvQmntpfsN1d311Eli2nQNKyBoIWC5ClVHPuJy2T7o+uTdEQSw38dzquo3JvipuWmELCTCbOV7e3xj08qCMc5s0kWnjqaB52SCx1CeKMsss0UO5YnCbyhGd27GBwpGTjzxW3Q+oLXWIr9oIp43sZTDPE4DMG2K/GwsDww4Bz6YzUCLoy0gj1CC2v8AUYLG+jKTWqSrs3GMRlwxUuGwAeGxnnFb9E6Wt9Jh1GKO8vJVv1US7yiYIjEe5dirtO1VHHHGQPNDUUdc6d2rhntb9HguYLV4jGrOGmwIyQGOASQMHkHyBX2Trayj0i5v5LHUU9llkiuIWjUPCUUM24ltnggj3ucjGTUa2/R/Z28Iji1PUgu+1cj9jybdt0fiPjwAcecfHJrbe9CWV5PcyzX+oZnuJLhwDHjMkYjZQNnjaoAP4hzg8mgrrjXZBrGpF9Sv109pdMNv2I4yU7zkbTlfwMdoJOSMnFXkHV1ncpePa2t5OltKYS0aqQzhyrD8XuYKnO/bxg+DUMdC24R1Oq6k242hJPZz/uzbo/8A6/iOfjWV30NY3VzPcyXt6LqR437o7WR23LKCNmHA3Y98MeBTCLbLXrWxvG01bKz1C5a/gNxEI4lOEEio247sDaWGfp4zXU1zWidIW2j31nc29/fSC0hmgiilMZXZK4dgSEBPKrjn09a6WgUpSopXnp1v++mv/wAwuP8AI1ehdeefXH76a/8AzC4/yNXZ4fcs8lLXylK7mXpPSlK8V9Coer6jDpdkbm4DMu9IkRANzu7BVUZ4yWYDnA55qZUDXNKtta02SxvRJ2nKuGjco6MrBlZWHghgCD9KCom6qlhvbWyfQNVF5cJK6RAwfhjdFY57mMe+p8+PrxVfofUxNxp9hbWeqXntb3rNLdSxdyEwzhHUgHBUFsDB8Aeau4enkj1Owv5NQv57izikhUyshDiQqW3e759xfGPH1OYtp0faWstpLDe34ntpriVJN6AnvuHkU4XBUsAfGR8cVcTULp/qaJtJtYrG21jVLjtmWRZWhM6IZXQFyWVTyrgBc8L/AH2XvXVjZzapFNZ3nc0+1kvHRe2WaONtrELvyPIIDYyDxW2w6KsrCW2ltb3UY5oVaMyLKqmWNnL7HwoBAZmIIAI3HBrRN0BpkiXSC61BEuIbi3ZVlUgRztukUZU+W5z558+MB8fruKK5lhuNF1WEQzQRTOwhKxrMwWNziQkqSccAkYOQK+y9f6ahvjHa3s8Volw5kiVCH7JxIB72R4bG7AO049Myrzo+1u2u2lv7/ddez9wgxj/4G3IR7nx5PxrI9IWYtNTtIry/is78SiS3WUbEMme4yAg43ZY48AkkAUmhBvOulgjugujX4uIGtdsUrRL3Y7iXtpIpDEYyDwcH6CpkPWNnLra6Z7NdCcTC3lYBWWKUpv2thicAEDcAVycZr5d9G2d207XF5fM0tvBb5DIpXsvvjYYX8QYk/A58YqXYdOQWOqS3tvd3ytOQ88Xd/ZzSBQvcYY4bAGcYBxyKYa06r1ZaabPfK9vcy2+nlBe3EYXZb7wCMgsCcKQx2g4BrLrC6v7S1tJrKK6ltVmzeizUNOItjcoD597bnHvYzistQ6WsL68uppWnWO82e1wI4Edzs4XeMZ8AA4IyAAcipWo6Ot5ei6S9vLWTtdl/Z3UB0znByDg8nkYPJ5pCqDSeqLW0sok9qu9XiaWP/fVVQEFxJ+wQ8gk7XTOB4wTjNQtK6i1LUtc0x/Zb5I7m5uQsSywiMW8RMe9hySd5Q8EeeMjg2N30zbaY0LaPp086iRHS2WZVhjlSIJHKwYgkAKg4JxgHaSM1P0fpi1079UyGWZ59PtBaKd+FccZYj1JIz/6GN3xRWdUarcDqyy0y1vdStoUs5Li59htBOxZmCxA5jfbnEpycfhrDQer5U0yzi1m2uJL4tJbyTRqgR540Z2T8XkBCCfwhgRmuksNIis9W1DURPPLPe7A4kK4RUztVcAEAbj5J81XydH6ZIt0khuGjnW4VU7nEPfJMpTjgsWPJzjOBgcVLiqVFi65sGtILmS1vIYHSKSV3VALcSRPL7/veioCcZxuX64wXrq2kNsINL1Kb2mf2eEoseHbtmTgl8eAQeeCDnGKkJ0TpfZv4rh7u5jvVZZRLL4LRiNmXAGGKgD6DIGASKsLTQoILiyuJbi6uZ7USbHncHJcAFiAAMgLgYAGCfjT6oga11haaOtqt3Z3hu5rY3TWqBDJGgxkH3sFsnGFJyQcZqTrvU1po7WCTRyyPehjCqlV3YAO0byMsc8KOTg4HFbtW0G31HULW/E91aXturRrNbSbS0bEEowIIIyAfGR6EU17QbbXLdbe8luBbbTHJEjDbKpIOGyDz7owwww5wRmsikuv0g6bCmoSR2l7PDZx3DmSNUIfsHEgHvZB4bG7AO049M3+naxFe6rf2CwyxyWaxOzNja4kBIxg5/hOc4qAvSVgsGpWomvP1ffiUS2fd/ZKZc9wqMZGSxOM4ycgCpGg9O2+jXVxcx3V7c3FxHHHLJcy7ywTO0+AAcMRxTDV1SlKilKUoFeefW/76a/8AzC4/yNXoZXnn1x++mv8A8wuP8jV2eH3LPJSUpSu5l6T0pSvFfQqr6l1ddC0afUZLaa5SHbujiKhsFgM+8QOM1aVz/X2n3OrdJahYWMLS3FwoRVVwh/ECTuJGOBQQ36x7M99a3mmTW15adtmSWeIIY5N+x9+7GCY2XHnOOOc1Hi67F1bLNp+i31yh06LVOJIlJifdwMtyw2nj71ZN0lptwY551u/bO4k/tDXDCYMqMoG4HwFdxgce8T5OapbboyOHqEwRxXsehppaWKFbsjcA7sUPvbsYYAHyMHn41Eu062stZnW20+11Ca1mKxG7ijYCNniEgJI8ABlBOeCR+dYaF1BJpv6M+ntUvo7q/lmt7RJGVlMjPLsTcSxGfeYZ5q4g6Y0+2vpbqy9ptGlUB4oJ2SJiFCBu3nbkKAPHoM5xQ9M2P+nLTRA1wLG17XaAk94CNgyDPrgqv9qYarX61WKxvprnTJ7eeyuTbTQzTxKFbtCUHduwdysuAMnJx8TU7UOp4rXp/TdWhsrm6hv3gSOOMorDvEBM7mA8sB59f61hc9HaXc3s11Mbszyzm5ZlnZPeMQiONuOCgC4+nx5qS/Tdi2i2GlhrhbSxeJ4R3SWBiIZMk5JwQP7UNVOndbG6vbW3n0S+thNczWRdpImCzxq7FOGJIIRsHx4rBOvrZ7e6kTT7iV7e4trdkhlifmdgqHdu28McEZ4PxHNWL9IabJt7jXLKLqW82mTgySKyP6eCrsMfWtEXQ2kQxduJ70A+z5zcs2ew26Lzn8JA/tz60w1jY9X+03lvaS6ZPbXMl9Jp8kckiHtyLAZgcqSCpQeR4J8VDt+uxK6TSaeYtPXTpNQnmaYFo1RyrAKBz+HPn1q1uekNOuZJpZJLxZZLz27uR3DRssvb7eVK4IBT3cfCsIeitFhSBEiuO1FDJb7GuHZZIpG3Mjgn3lyTwaCGOuALdZG0TUwz3EMCKUVAxlztwzlQcEYIGcEj05rbZ9Y+2IIbfSro6ruuFaxaSMMvZKhzvztxl0xzzuHjnExOlLFLK3tfaNQeK3mjni7t08hVkOUGWJ4H39c1Va/0ep7E+jm5XUPbjcNOJyhVXI7q5DKdrBVGOccEDIphqLJ1R+rerLg6peXcNn7NLO1o/ZYQ9uNXJ9wlwNu7yTkg4wMZ6PSuoBeamthc2c1ncyWwvIldlYPHkA8g8MCVyPqME1GforRnvHuJIrhy8kkjRvcOyMZE2yZUnBDDyKsNJ0Gy0yYTQd6SYRC3WSeVpGSMHIQEnx9zxknAoa5e+6il1DW+mLvT4rtdNlvLmPeswAuQkE3BTPILICpPw9M1e9OdRW/UX6whijaJ7VlR9squCHQMMMhIyM4ODwR6+aj/AOhdE91dl32Elkmjt/apBFG0iur7VzgAiRuPHPGKnaJ03Y6K872LXQeaNInaSdpMhBhT7xPIHH9KDkelOtTp3TVgNdtr/aunS3gvZJFlM4iI3jG4tu99cZ81dw9aJLLHbtpl5BdyuyRrOvbRwsZkJDkYPAIxjyD6c1JXozSRbWltIs81tbW0tokUkmVMUuN6n45wOfTFVPUXSMsqaXb2smo3Njbu8rk3YknD7dqY72UKgF855ztI9aDLS/0gw6gLTt6RqBaZIJJBGnc7SzH3Ccf8uGbxgH18VlD11LcTQxW/T2ou1w9zFDmWAbpIHKup9/j8J5+lTdD6ZeP2e/1a5uG1gJsnlt5jGsyKzGMSKm1WKqQM7Rnn04qTbdKWNtJavDLdB7aWeaMmTOHmJMh8c5LE/TNJoi1cnXdpNpjX9rY3U1vBYRaldYKhoYpAWAxn3mAViQPQeeQKzbrZBfSRLpV6bWK/isJLrfHtVpVjMbY3bip7qemRmpEPROjw2iWyJcCAWy2ciiZgJoVztR8fiA3EfHBIzg1vm6VsJTdEvcr7TdxXzhZMDux7QhHHAHbTj/lH1yw1RDrC00TQbi9lgv5IUurvue1XMZdTHKQ6rlve9dqj+FfI4z3gOQD8a5W46E0a5ikjnF26yC4V8XDLuWdg0inbjgsAfp+XFdPBGIYUjVmYIoUFzknHxPqaDOvPPrj99Nf/AJhcf5Gr0Mrzz64/fTX/AOYXH+Rq6/E7lOSkr5mlK7WXpRSlK8Z9CqzqTVhoukvetF3VWSOM+9tVd7qu5j6Ku7JPoAas6o+stJn1rQ2srYx5aaJ3V5Gj3qrhiA65KHjhgCR96DkLrqOa61fSNUvbdbays729jV7e5aVbiOO3lLNjaoI90Y885/rj1f1JdXejW1vPZC1nultr+0aG6YgqLmFWRyFG04lXxuByfhz0OidKduTvaxLPcmGcy2cT3ss4t1MYRl3tgvn3vxA4DEeKnDpHQ/ZJLU2CtA6rHseR22orBlRcn3VBAIVcDgcVUU9x1ne2s2oWd3pdumo2kiAIt2zxyo8bOpUiPeW9xgVCEjz45r70jqI1Xqq71CMSxw3ujafdiJ3LBC7T+B4BwFBx5xV0/SujSRoslmXZJe/3GlcyF9uzLOTub3fdwSRjjxWmDo3QoABHZvtCQxhWuJWULE2+MAFsAK3I/M/E0wdDSlKilKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFeeXXH769QfzC4/yNXobXnl1x++nUH8wuP8AI1dfidyzyUlKUrtZelFKUrx30K+MyqAWYAE4GT619qDrFpHfWiwzbgvcVsqcEEHIIPxB5oJqsGUMpBUjII9a+1xEGmRxQrGJ7g42LuL847ZP5fTPwqTb2arZahmWZt8ER5bxx4AHpx/3q0luuPAyaxDqSAGBJ54NchJpEMsV4ss1w+e4CWcc7tueMY8HH5cVhLpyNdl+9MGyvIYcbsD4enkfA81fVLdpTxXL3LPcpHevIyytpzMVQ4XJUk/X/wBCotlp0c9xbmWSVlMDEpkbfeY8Yx4Hp8KyrsQylSwYED1Br6CCAQcg1ysNhEvT1yAz4dwxyR4yPd8eOfFLWzWOykAllIF4DgkYP8PjH1z+YBq0W6ncu7bkbsZxnmvtcUtgiO1z3ZmmjKqrMQSAgOPT12jPxya3xWS2qK0c0xZbWU5Zhkn3uScc+n9hQt11K4Ke32WsLiWUl0UkEjAy/jGPHHir24s1F5ft3ZiWtR5f6Y//AJ+5+NKLX4IYZBBHxFfN679m5d+M7c84rj7GxSO7hKyy8SwYGRgYUnxjHqRWcmlxSahcyNLMG3SjhgPQkHx5GePyFKLdasiOpZWUqMgkHjjzWQIIyDkGuOtdLiYO7TTlw4wdw4wW8ccZPJr5NZLgKZZiEaf+LBbKepAz64/oKUW7FiFBLEADkk1iZYwcF1B44z8fH/eubvNNjmhhZ5Zt628SBgwB4Pnx5NQjpse9j3ZubnB5HOMcnjzx5pWluzVgwypB/KvtcUdPjjsHhSSUIbdF8jIAYDjjiplupFrqdtvcxqTMMnJzuOQT6g7ec/E0ot1NK0WEfasbePcz7I1XcxyTgeT9a31FKUpQK88uuP306g/mFx/kavQ2vPLrj99OoP5hcf5Grr8TuWeSkpSvldqP/9k=" + }, + { + "timing": 1125, + "timestamp": 27711497153, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1500, + "timestamp": 27711872153, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1875, + "timestamp": 27712247153, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2250, + "timestamp": 27712622153, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2625, + "timestamp": 27712997153, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 3000, + "timestamp": 27713372153, + "data": "data:image/jpeg;base64,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" + } + ] + } + }, + "final-screenshot": { + "id": "final-screenshot", + "title": "Final Screenshot", + "description": "The last screenshot captured of the pageload.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "screenshot", + "timing": 724, + "timestamp": 27711096368, + "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACMAPoDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAQFAgMGCQEHCP/EAD4QAAIBAwMDAQYCCQEIAwEAAAECAwAEEQUSIQYTMUEUIlFSYaEycQcVIzZCdYGRsxYYJFZilLHS8TNDwcL/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIEBQP/xAAiEQEAAwACAgICAwAAAAAAAAAAAREhAjEEEgMiQVEUUmH/2gAMAwEAAhEDEQA/AP6ppSlApSlBgkqPJIi53RkBsggcjPn1pDNHMrNE4YKxQkehBwR/Qivzq+6e1m7utYmFvIZJrW8gt3kuFJV2KCFhzwAEL5/hLtgVstek76O4ZXtxHYub0vHbsiuzzy70fJ4yi5XPkFjjIrfrH7S36JmmRnGRn4V+K9XdAaxqWl9P2djYQ99bq6N009wRGI2WXtbyhyQN4GAPI8YrdH+j3qu31nSZDq4uYbS3ijecudxKIFYYPqSDz9ea16cf7Fv2JnVcbmAycDJ8n4VlX4l+ib9HutaLPpt11AixKyRM9qjtJiWNZMSSkkgPl1A25GF9K/baxz4xxmomyGiS5COVC5xWHtf/ACfeqnqLTpNRVI47mS1ZJe5vTOfwkY4Ix+Lz9K55eldRWOFTr87PG27eyNljhgc4ced3ke9x+IcY1HGJjUt3kMwlyMYIrbVfpETwwRxySNM6RqrSMOXIHk/U1X3nT88urT31vqc9v3fMajIHuoMjkc+5n68Z4BBxMaroKVzidPXaRCNNWmGEChirFgQuN34vP18/X0q00eyuLGCRLq9e8dn3B3GCBgceT8PvUE+lVmt6U2piApdSW7xMSGQZznGQR68ZH9c+lVo6e1ERBDr92SFxuI8tnz5+HGP61R0tK5p+m7tpGYa1cqPd2AAgJggnADYPj1z59at9IsprGBkuLuW7diDvkzwAAMYyfgT/AFqCdSoGsae9+luI5+w0UokDbNx4+AzjP1Oaqk6dvlMh/XdyC+4kqGHJXaPLHxx/+k8YDpKVqtImhtYYpJGldECtI3lyB5P51topSlKBSlKBSlKBSlKDV3T8v3p3T8v3rXSqjZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2GTPlPvXzuD5B/esKUGwS48L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oNndPy/endPy/etdKDZ3T8v3p3T8v3rXSg2d0/L96d0/L9610oFKUoFQ9U1G20u2FxeuY4S4TdtJAJOBnHgfU1MrTdW0F3GI7qGKaMENtkUMMjwcH1oKVur9GWWONriQPJOtuuYXGWJIB8eCQRnxxWWldWaRqtxbw2Fw8sk5kCfs2A9wKWySOPxL/f86sTpWnltxsbUt3e/nsrnufP4/F9fNZJptjHLFKllbLLFntuIlBTIAODjjIAH9K19RB/1Npfs2oTmdxFY8zkxsMDcy5GRyMqwyPgaiJ1toUhmEd1I7RJG7KsDlsSIHXAxn8Jz/f4VeJY2iCYJbQKJm3y4jA3t8W+J+prCfTrG47vfs7aXu47m+JW34xjORzjA/sKYK9ep9Na9W0SSV53LBVWJju2orkjjxtdf71npnUem6lPBDaySmSaITIGiZQVIDDkjGdpBx5wam/qyxEyyiytu6rmRX7S7g54LA48nAyfpWcVlaxTLNFbQJMsYiV1jAYIPCg/D6eKYJFKUrIUpSgUpSgUpSgUpSgUpSgUpSgUpSgUpSgV+TfpI/TfonRuryaVDaT6pqEOO8sThI4if4Sxz730Ar9Zrz367dpOt+oXdizNqFwST6/tGro8f4o+SZ9kmX75/tO2/wDwtL/1w/8ACn+07b/8LS/9cP8Awr+Z6Zrs/jfH+mbl6SUpSvLbKpur9Xk0TQZryCNJLgyRQQq/4e5JIsalsegLAn6Crmoesaba6xps9hfoXt5l2sASCOcggjkEEAg+hFBz+o3mpdPXukPdX51C1vrpLOZHhRDE7g7WTaB7u4YIbJwc54529U63Np2saXbwSlITHNd3m2Pe3YjAHHwJd0H5bvzEmLp4veWU+p6ld6j7E3ct0mWNVV8Fd52KNzAEgenOcZ5rDVOmItSvNUnububF/ZrYlVAHbiBYsFOPLbjn+nwrUV+UQ5uubE2E9xZWl7cSItuyRGLtmTvsVjwW+LAj7+Oa33/W2kWPtHeNwTbxSSyduIvgRkBxx6qWAJ8ZBGeDjGfo6CW4nl9vvIxLeRXexNgC9tNioPdyFAAPxBGRg18g6MtYrG9svbLlrS6nM7REIBhpe66khcsGOVy2TtOBWvoMLrrvS7V5VmttUVo4ZLg5snGY027mHHIAcHNTJOrtKWQqHmkTc8ayJEWV5FQuyL8WCg/TgjOQRVdNo8mvdR6y18bqDT+zFYiLt7RcRgl3wxGdrFtpx5C/UGoXUvSy2mjXCWDXs8krTRW8aIGFt7TIe9KMDJKq74J8DgeeVcR0Wg9UafrbSC1FxHst4rv9vEYwYpN21hnyPdYf0qF0t1Raa9ql/wCx363EQijlt4FQD9kcjfnPJLA8HGABxzk2em6Hb2ntbyO9xNdIsUjuAP2aghUAAACjLHHxY1h01obaFZraLqF1d20SCKBJ1jzEg4A3KoLYGBk5PFYmrw1Fi6w02SG3l7d2qTQXE6/stxxA22QYBJyD6DzUI/pD0YRySNDqaxxxR3DObKQAQvnbLnH4ODz9PFfIOho4LgOmr35ijW6SGFliKRrcHc4/BuODyMn0H1z9m6Hilsbm1bULgRz6ZFpTEIuRHGWww4/F7zfT6Uw1dazr1tpNzZ280V1NcXhcQpBCX3FV3EZ8A4B8n0qth640idrRLdb+WS5R3VEtJCU2SCOQPx7pVjznx/atXVdne3Gu9LG1e5T2e4leW4ihDhAYWUbsggAlgKkad0ja2F/bXMVxM4ihuInSQKe8Z5BJI7EAcllB4wByMUEPWestOOiXMsV3e2AktHu7a7W13l4lxmSNSCGA3KcEDg5xjmt9311otrd3Np33nuYBIpSMAl3jQuyDn8W0HzgZBGcjFVWv9FbOlprOzmur2aKwk03T45doECSBVySAM4AXJPOFPknm8t+lY4J9REV/dLY3xkeWzxGUDyDDlWK7hkknGcZ/tSf8GMfWFkbKK4ltb2N2tReyQiMO8MJ/jbaTxwcAZJwcDg10NvNHc28c8DrJDIodHU5DKRkEVy0HRawxBV1e/wC61kNPll2xAyQrnaMbMArubBHzHOeMdPZWsNlZwWtsgSCCNYo0H8KqMAf2FJoi26lKVFKUpQKUpQKUpQK89et/301/+YXH+Rq9Cq89Ot/301/+YXH+Rq7PD7lnkpTSlfM13svSalKV4j6FU3WN4un9NX109xd2yRJuaa0VWlQZGSoYEf39KuaruodJi1zRbvTbiaaGG5Ttu8O3cB64yCPtQVU3WNlBqUlm9pqGIr2Owln7Q7ccrhCmTnOG7ijIB5POKrNW6q361oEtm91FpD3lxFcXJEfYmWOCYt67+GjyDgA4PJ4qyn6Qhn9tL6jfZu72C/cjt8SQ7NoHueP2aZH0+taE6EsVEMJvr9rCC4kuIrJmjMSdxZFdAdm7aRK/BbjPGKqNdv8ApD0i5g7ltDdzEyQRqsYRy3eYrGchsDJGCCQRxkDNfZ/0haRbK8d1FdW99HK8L2U3bSVSqq5OS+wja6EYY53ADJ4qL1D01eW3Tdnp+n3uq33bvrR42Ywl7eKOVWJBKgNhV/i3E4HmrJ+jLX2pb6HUNQg1butK9+hj7sm5VQqwKFNu2NBjbxtB880w1rn680yMsyW2oSwItvJJOkHuRxzfgc5IOPjgEj4VG6h6lke/0qPSva1txqy2dxcqsfZcjcHjOTu4IxkDGRjNWGo9IQX51QzahfD9YxwxS7e3wIiSuMp5OTnOfPpWodE2q3G5NS1Fbf239YC1DRmNZslmIym7DEsSM45OMUyzaR4f0jaHNb3E8JuJYYkWUNEqvvjZwm4BWJHLDhgDg5xW2breJSscWkak917etg9swjR1ZozIrcvtKlRxz+eKzHRcC6FLoy6pqQ047VhhLRn2dFYMFQlMkcAe8WOBitl30fBc3d1dHUb+O4muortZI+3mKSNNg2goRgrwQc/0phrPTOstK1HXDpVs7m43SRhsqQXjOHXAYsCCDyQAcHBNZXvVlnbXTRrbXk8KXcdhJcRKpjSdyoCHLAnl1yQCBnGcg1v0Xp9NIu55LW/vTayyPN7G5QxLI53Ow93cMsScbsZJwBXP9S9Ly27+0aO+oSrc6ta309khjMQKzI0kg3DcOEzgNjPpzT8m0sl640xbaC5uYrq2t5IrqVpJVXEYt2KyBtrE5yOMZzW3T+pLXqDStUOmSzQT20ZBbMbMhK5VgQWQ/fkYIqEegNOkeRbi91Ce0b2kC1d07aLcEmRQQgbBJyMnI45q+07SntNOktLjUr2+DqUEtyU3quMYG1VB/Mgn41J6WO3JaP17Fa9N6fJrVpqPtZ0uO+LlI8XKhU7joQ2OC4JBwcHgVdX/AFlYWNzc280Fz34bpLQJ+zHddou4NpLAcqD5IOeMZrQvQmntpfsN1d311Eli2nQNKyBoIWC5ClVHPuJy2T7o+uTdEQSw38dzquo3JvipuWmELCTCbOV7e3xj08qCMc5s0kWnjqaB52SCx1CeKMsss0UO5YnCbyhGd27GBwpGTjzxW3Q+oLXWIr9oIp43sZTDPE4DMG2K/GwsDww4Bz6YzUCLoy0gj1CC2v8AUYLG+jKTWqSrs3GMRlwxUuGwAeGxnnFb9E6Wt9Jh1GKO8vJVv1US7yiYIjEe5dirtO1VHHHGQPNDUUdc6d2rhntb9HguYLV4jGrOGmwIyQGOASQMHkHyBX2Trayj0i5v5LHUU9llkiuIWjUPCUUM24ltnggj3ucjGTUa2/R/Z28Iji1PUgu+1cj9jybdt0fiPjwAcecfHJrbe9CWV5PcyzX+oZnuJLhwDHjMkYjZQNnjaoAP4hzg8mgrrjXZBrGpF9Sv109pdMNv2I4yU7zkbTlfwMdoJOSMnFXkHV1ncpePa2t5OltKYS0aqQzhyrD8XuYKnO/bxg+DUMdC24R1Oq6k242hJPZz/uzbo/8A6/iOfjWV30NY3VzPcyXt6LqR437o7WR23LKCNmHA3Y98MeBTCLbLXrWxvG01bKz1C5a/gNxEI4lOEEio247sDaWGfp4zXU1zWidIW2j31nc29/fSC0hmgiilMZXZK4dgSEBPKrjn09a6WgUpSopXnp1v++mv/wAwuP8AI1ehdeefXH76a/8AzC4/yNXZ4fcs8lLXylK7mXpPSlK8V9Coer6jDpdkbm4DMu9IkRANzu7BVUZ4yWYDnA55qZUDXNKtta02SxvRJ2nKuGjco6MrBlZWHghgCD9KCom6qlhvbWyfQNVF5cJK6RAwfhjdFY57mMe+p8+PrxVfofUxNxp9hbWeqXntb3rNLdSxdyEwzhHUgHBUFsDB8Aeau4enkj1Owv5NQv57izikhUyshDiQqW3e759xfGPH1OYtp0faWstpLDe34ntpriVJN6AnvuHkU4XBUsAfGR8cVcTULp/qaJtJtYrG21jVLjtmWRZWhM6IZXQFyWVTyrgBc8L/AH2XvXVjZzapFNZ3nc0+1kvHRe2WaONtrELvyPIIDYyDxW2w6KsrCW2ltb3UY5oVaMyLKqmWNnL7HwoBAZmIIAI3HBrRN0BpkiXSC61BEuIbi3ZVlUgRztukUZU+W5z558+MB8fruKK5lhuNF1WEQzQRTOwhKxrMwWNziQkqSccAkYOQK+y9f6ahvjHa3s8Volw5kiVCH7JxIB72R4bG7AO049Myrzo+1u2u2lv7/ddez9wgxj/4G3IR7nx5PxrI9IWYtNTtIry/is78SiS3WUbEMme4yAg43ZY48AkkAUmhBvOulgjugujX4uIGtdsUrRL3Y7iXtpIpDEYyDwcH6CpkPWNnLra6Z7NdCcTC3lYBWWKUpv2thicAEDcAVycZr5d9G2d207XF5fM0tvBb5DIpXsvvjYYX8QYk/A58YqXYdOQWOqS3tvd3ytOQ88Xd/ZzSBQvcYY4bAGcYBxyKYa06r1ZaabPfK9vcy2+nlBe3EYXZb7wCMgsCcKQx2g4BrLrC6v7S1tJrKK6ltVmzeizUNOItjcoD597bnHvYzistQ6WsL68uppWnWO82e1wI4Edzs4XeMZ8AA4IyAAcipWo6Ot5ei6S9vLWTtdl/Z3UB0znByDg8nkYPJ5pCqDSeqLW0sok9qu9XiaWP/fVVQEFxJ+wQ8gk7XTOB4wTjNQtK6i1LUtc0x/Zb5I7m5uQsSywiMW8RMe9hySd5Q8EeeMjg2N30zbaY0LaPp086iRHS2WZVhjlSIJHKwYgkAKg4JxgHaSM1P0fpi1079UyGWZ59PtBaKd+FccZYj1JIz/6GN3xRWdUarcDqyy0y1vdStoUs5Li59htBOxZmCxA5jfbnEpycfhrDQer5U0yzi1m2uJL4tJbyTRqgR540Z2T8XkBCCfwhgRmuksNIis9W1DURPPLPe7A4kK4RUztVcAEAbj5J81XydH6ZIt0khuGjnW4VU7nEPfJMpTjgsWPJzjOBgcVLiqVFi65sGtILmS1vIYHSKSV3VALcSRPL7/veioCcZxuX64wXrq2kNsINL1Kb2mf2eEoseHbtmTgl8eAQeeCDnGKkJ0TpfZv4rh7u5jvVZZRLL4LRiNmXAGGKgD6DIGASKsLTQoILiyuJbi6uZ7USbHncHJcAFiAAMgLgYAGCfjT6oga11haaOtqt3Z3hu5rY3TWqBDJGgxkH3sFsnGFJyQcZqTrvU1po7WCTRyyPehjCqlV3YAO0byMsc8KOTg4HFbtW0G31HULW/E91aXturRrNbSbS0bEEowIIIyAfGR6EU17QbbXLdbe8luBbbTHJEjDbKpIOGyDz7owwww5wRmsikuv0g6bCmoSR2l7PDZx3DmSNUIfsHEgHvZB4bG7AO049M3+naxFe6rf2CwyxyWaxOzNja4kBIxg5/hOc4qAvSVgsGpWomvP1ffiUS2fd/ZKZc9wqMZGSxOM4ycgCpGg9O2+jXVxcx3V7c3FxHHHLJcy7ywTO0+AAcMRxTDV1SlKilKUoFeefW/76a/8AzC4/yNXoZXnn1x++mv8A8wuP8jV2eH3LPJSUpSu5l6T0pSvFfQqr6l1ddC0afUZLaa5SHbujiKhsFgM+8QOM1aVz/X2n3OrdJahYWMLS3FwoRVVwh/ECTuJGOBQQ36x7M99a3mmTW15adtmSWeIIY5N+x9+7GCY2XHnOOOc1Hi67F1bLNp+i31yh06LVOJIlJifdwMtyw2nj71ZN0lptwY551u/bO4k/tDXDCYMqMoG4HwFdxgce8T5OapbboyOHqEwRxXsehppaWKFbsjcA7sUPvbsYYAHyMHn41Eu062stZnW20+11Ca1mKxG7ijYCNniEgJI8ABlBOeCR+dYaF1BJpv6M+ntUvo7q/lmt7RJGVlMjPLsTcSxGfeYZ5q4g6Y0+2vpbqy9ptGlUB4oJ2SJiFCBu3nbkKAPHoM5xQ9M2P+nLTRA1wLG17XaAk94CNgyDPrgqv9qYarX61WKxvprnTJ7eeyuTbTQzTxKFbtCUHduwdysuAMnJx8TU7UOp4rXp/TdWhsrm6hv3gSOOMorDvEBM7mA8sB59f61hc9HaXc3s11Mbszyzm5ZlnZPeMQiONuOCgC4+nx5qS/Tdi2i2GlhrhbSxeJ4R3SWBiIZMk5JwQP7UNVOndbG6vbW3n0S+thNczWRdpImCzxq7FOGJIIRsHx4rBOvrZ7e6kTT7iV7e4trdkhlifmdgqHdu28McEZ4PxHNWL9IabJt7jXLKLqW82mTgySKyP6eCrsMfWtEXQ2kQxduJ70A+z5zcs2ew26Lzn8JA/tz60w1jY9X+03lvaS6ZPbXMl9Jp8kckiHtyLAZgcqSCpQeR4J8VDt+uxK6TSaeYtPXTpNQnmaYFo1RyrAKBz+HPn1q1uekNOuZJpZJLxZZLz27uR3DRssvb7eVK4IBT3cfCsIeitFhSBEiuO1FDJb7GuHZZIpG3Mjgn3lyTwaCGOuALdZG0TUwz3EMCKUVAxlztwzlQcEYIGcEj05rbZ9Y+2IIbfSro6ruuFaxaSMMvZKhzvztxl0xzzuHjnExOlLFLK3tfaNQeK3mjni7t08hVkOUGWJ4H39c1Va/0ep7E+jm5XUPbjcNOJyhVXI7q5DKdrBVGOccEDIphqLJ1R+rerLg6peXcNn7NLO1o/ZYQ9uNXJ9wlwNu7yTkg4wMZ6PSuoBeamthc2c1ncyWwvIldlYPHkA8g8MCVyPqME1GforRnvHuJIrhy8kkjRvcOyMZE2yZUnBDDyKsNJ0Gy0yYTQd6SYRC3WSeVpGSMHIQEnx9zxknAoa5e+6il1DW+mLvT4rtdNlvLmPeswAuQkE3BTPILICpPw9M1e9OdRW/UX6whijaJ7VlR9squCHQMMMhIyM4ODwR6+aj/AOhdE91dl32Elkmjt/apBFG0iur7VzgAiRuPHPGKnaJ03Y6K872LXQeaNInaSdpMhBhT7xPIHH9KDkelOtTp3TVgNdtr/aunS3gvZJFlM4iI3jG4tu99cZ81dw9aJLLHbtpl5BdyuyRrOvbRwsZkJDkYPAIxjyD6c1JXozSRbWltIs81tbW0tokUkmVMUuN6n45wOfTFVPUXSMsqaXb2smo3Njbu8rk3YknD7dqY72UKgF855ztI9aDLS/0gw6gLTt6RqBaZIJJBGnc7SzH3Ccf8uGbxgH18VlD11LcTQxW/T2ou1w9zFDmWAbpIHKup9/j8J5+lTdD6ZeP2e/1a5uG1gJsnlt5jGsyKzGMSKm1WKqQM7Rnn04qTbdKWNtJavDLdB7aWeaMmTOHmJMh8c5LE/TNJoi1cnXdpNpjX9rY3U1vBYRaldYKhoYpAWAxn3mAViQPQeeQKzbrZBfSRLpV6bWK/isJLrfHtVpVjMbY3bip7qemRmpEPROjw2iWyJcCAWy2ciiZgJoVztR8fiA3EfHBIzg1vm6VsJTdEvcr7TdxXzhZMDux7QhHHAHbTj/lH1yw1RDrC00TQbi9lgv5IUurvue1XMZdTHKQ6rlve9dqj+FfI4z3gOQD8a5W46E0a5ikjnF26yC4V8XDLuWdg0inbjgsAfp+XFdPBGIYUjVmYIoUFzknHxPqaDOvPPrj99Nf/AJhcf5Gr0Mrzz64/fTX/AOYXH+Rq6/E7lOSkr5mlK7WXpRSlK8Z9CqzqTVhoukvetF3VWSOM+9tVd7qu5j6Ku7JPoAas6o+stJn1rQ2srYx5aaJ3V5Gj3qrhiA65KHjhgCR96DkLrqOa61fSNUvbdbays729jV7e5aVbiOO3lLNjaoI90Y885/rj1f1JdXejW1vPZC1nultr+0aG6YgqLmFWRyFG04lXxuByfhz0OidKduTvaxLPcmGcy2cT3ss4t1MYRl3tgvn3vxA4DEeKnDpHQ/ZJLU2CtA6rHseR22orBlRcn3VBAIVcDgcVUU9x1ne2s2oWd3pdumo2kiAIt2zxyo8bOpUiPeW9xgVCEjz45r70jqI1Xqq71CMSxw3ujafdiJ3LBC7T+B4BwFBx5xV0/SujSRoslmXZJe/3GlcyF9uzLOTub3fdwSRjjxWmDo3QoABHZvtCQxhWuJWULE2+MAFsAK3I/M/E0wdDSlKilKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFKUoFeeXXH769QfzC4/yNXobXnl1x++nUH8wuP8AI1dfidyzyUlKUrtZelFKUrx30K+MyqAWYAE4GT619qDrFpHfWiwzbgvcVsqcEEHIIPxB5oJqsGUMpBUjII9a+1xEGmRxQrGJ7g42LuL847ZP5fTPwqTb2arZahmWZt8ER5bxx4AHpx/3q0luuPAyaxDqSAGBJ54NchJpEMsV4ss1w+e4CWcc7tueMY8HH5cVhLpyNdl+9MGyvIYcbsD4enkfA81fVLdpTxXL3LPcpHevIyytpzMVQ4XJUk/X/wBCotlp0c9xbmWSVlMDEpkbfeY8Yx4Hp8KyrsQylSwYED1Br6CCAQcg1ysNhEvT1yAz4dwxyR4yPd8eOfFLWzWOykAllIF4DgkYP8PjH1z+YBq0W6ncu7bkbsZxnmvtcUtgiO1z3ZmmjKqrMQSAgOPT12jPxya3xWS2qK0c0xZbWU5Zhkn3uScc+n9hQt11K4Ke32WsLiWUl0UkEjAy/jGPHHir24s1F5ft3ZiWtR5f6Y//AJ+5+NKLX4IYZBBHxFfN679m5d+M7c84rj7GxSO7hKyy8SwYGRgYUnxjHqRWcmlxSahcyNLMG3SjhgPQkHx5GePyFKLdasiOpZWUqMgkHjjzWQIIyDkGuOtdLiYO7TTlw4wdw4wW8ccZPJr5NZLgKZZiEaf+LBbKepAz64/oKUW7FiFBLEADkk1iZYwcF1B44z8fH/eubvNNjmhhZ5Zt628SBgwB4Pnx5NQjpse9j3ZubnB5HOMcnjzx5pWluzVgwypB/KvtcUdPjjsHhSSUIbdF8jIAYDjjiplupFrqdtvcxqTMMnJzuOQT6g7ec/E0ot1NK0WEfasbePcz7I1XcxyTgeT9a31FKUpQK88uuP306g/mFx/kavQ2vPLrj99OoP5hcf5Grr8TuWeSkpSvldqP/9k=" + } + }, + "total-blocking-time": { + "id": "total-blocking-time", + "title": "Total Blocking Time", + "description": "Sum of all time periods between FCP and Time to Interactive, when task length exceeded 50ms, expressed in milliseconds. [Learn more about the Total Blocking Time metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/).", + "score": 1, + "scoreDisplayMode": "numeric", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "scoringOptions": { + "p10": 150, + "median": 350 + } + }, + "max-potential-fid": { + "id": "max-potential-fid", + "title": "Max Potential First Input Delay", + "description": "The maximum potential First Input Delay that your users could experience is the duration of the longest task. [Learn more about the Maximum Potential First Input Delay metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-max-potential-fid/).", + "score": 1, + "scoreDisplayMode": "numeric", + "numericValue": 20, + "numericUnit": "millisecond", + "displayValue": "20 ms" + }, + "cumulative-layout-shift": { + "id": "cumulative-layout-shift", + "title": "Cumulative Layout Shift", + "description": "Cumulative Layout Shift measures the movement of visible elements within the viewport. [Learn more about the Cumulative Layout Shift metric](https://web.dev/articles/cls).", + "score": 0.75, + "scoreDisplayMode": "numeric", + "numericValue": 0.15287, + "numericUnit": "unitless", + "displayValue": "0.153", + "scoringOptions": { + "p10": 0.1, + "median": 0.25 + }, + "details": { + "type": "debugdata", + "items": [ + { + "cumulativeLayoutShiftMainFrame": 0.15287, + "newEngineResult": { + "cumulativeLayoutShift": 0.15287, + "cumulativeLayoutShiftMainFrame": 0.15287 + }, + "newEngineResultDiffered": false + } + ] + } + }, + "errors-in-console": { + "id": "errors-in-console", + "title": "Browser errors were logged to the console", + "description": "Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns. [Learn more about this errors in console diagnostic audit](https://developer.chrome.com/docs/lighthouse/best-practices/errors-in-console/)", + "score": 0, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "sourceLocation", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "description", + "valueType": "code", + "label": "Description" + } + ], + "items": [ + { + "source": "network", + "description": "Failed to load resource: net::ERR_ADDRESS_UNREACHABLE", + "sourceLocation": { + "type": "source-location", + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/auth/me/", + "urlProvider": "network", + "line": 0, + "column": 0 + } + } + ] + } + }, + "server-response-time": { + "id": "server-response-time", + "title": "Initial server response time was short", + "description": "Keep the server response time for the main document short because all other requests depend on it. [Learn more about the Time to First Byte metric](https://developer.chrome.com/docs/lighthouse/performance/time-to-first-byte/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 14.853, + "numericUnit": "millisecond", + "displayValue": "Root document took 10 ms", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "responseTime", + "valueType": "timespanMs", + "label": "Time Spent" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "responseTime": 14.853 + } + ], + "overallSavingsMs": 0 + }, + "guidanceLevel": 1 + }, + "interactive": { + "id": "interactive", + "title": "Time to Interactive", + "description": "Time to Interactive is the amount of time it takes for the page to become fully interactive. [Learn more about the Time to Interactive metric](https://developer.chrome.com/docs/lighthouse/performance/interactive/).", + "score": 1, + "scoreDisplayMode": "numeric", + "numericValue": 748.77315, + "numericUnit": "millisecond", + "displayValue": "0.7 s" + }, + "user-timings": { + "id": "user-timings", + "title": "User Timing marks and measures", + "description": "Consider instrumenting your app with the User Timing API to measure your app's real-world performance during key user experiences. [Learn more about User Timing marks](https://developer.chrome.com/docs/lighthouse/performance/user-timings/).", + "score": null, + "scoreDisplayMode": "notApplicable", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "timingType", + "valueType": "text", + "label": "Type" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 0.01, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 0.01, + "label": "Duration" + } + ], + "items": [] + }, + "guidanceLevel": 2 + }, + "critical-request-chains": { + "id": "critical-request-chains", + "title": "Avoid chaining critical requests", + "description": "The Critical Request Chains below show you what resources are loaded with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load. [Learn how to avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "6 chains found", + "details": { + "type": "criticalrequestchain", + "chains": { + "C45C9DBBC5DE2A1F79093E7EDEFBEFC7": { + "request": { + "url": "http://localhost:4173/", + "startTime": 27710.385129, + "endTime": 27710.401032, + "responseReceivedTime": 27710.400433999996, + "transferSize": 1284 + }, + "children": { + "187074.3": { + "request": { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "startTime": 27710.460381999997, + "endTime": 27710.509519, + "responseReceivedTime": 27710.496691999997, + "transferSize": 11584 + }, + "children": { + "187074.18": { + "request": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2", + "startTime": 27710.865567, + "endTime": 27710.886108, + "responseReceivedTime": 27710.884180000005, + "transferSize": 58404 + } + }, + "187074.15": { + "request": { + "url": "http://localhost:4173/assets/inter-latin-wght-normal-Dx4kXJAl.woff2", + "startTime": 27710.867945, + "endTime": 27710.887258, + "responseReceivedTime": 27710.884958000002, + "transferSize": 48576 + } + }, + "187074.23": { + "request": { + "url": "http://localhost:4173/assets/montserrat-latin-wght-normal-l_AIctKy.woff2", + "startTime": 27710.868205, + "endTime": 27710.902527, + "responseReceivedTime": 27710.899501999997, + "transferSize": 38276 + } + }, + "187074.26": { + "request": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2", + "startTime": 27710.868621, + "endTime": 27710.904319, + "responseReceivedTime": 27710.900916000006, + "transferSize": 64840 + } + } + } + }, + "187074.8": { + "request": { + "url": "http://localhost:4173/site.webmanifest", + "startTime": 27710.475641, + "endTime": 27710.51798, + "responseReceivedTime": 27710.517065, + "transferSize": 796 + } + }, + "187074.2": { + "request": { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "startTime": 27710.453653, + "endTime": 27710.576225, + "responseReceivedTime": 27710.491787, + "transferSize": 116210 + } + } + } + } + }, + "longestChain": { + "duration": 519.1900000050664, + "length": 3, + "transferSize": 64840 + } + }, + "guidanceLevel": 1 + }, + "redirects": { + "id": "redirects", + "title": "Avoid multiple page redirects", + "description": "Redirects introduce additional delays before the page can be loaded. [Learn how to avoid page redirects](https://developer.chrome.com/docs/lighthouse/performance/redirects/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0 + }, + "guidanceLevel": 2 + }, + "image-aspect-ratio": { + "id": "image-aspect-ratio", + "title": "Displays images with correct aspect ratio", + "description": "Image display dimensions should match natural aspect ratio. [Learn more about image aspect ratio](https://developer.chrome.com/docs/lighthouse/best-practices/image-aspect-ratio/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Displayed)" + }, + { + "key": "actualAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Actual)" + } + ], + "items": [] + } + }, + "image-size-responsive": { + "id": "image-size-responsive", + "title": "Serves images with appropriate resolution", + "description": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn how to provide responsive images](https://web.dev/articles/serve-responsive-images).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedSize", + "valueType": "text", + "label": "Displayed size" + }, + { + "key": "actualSize", + "valueType": "text", + "label": "Actual size" + }, + { + "key": "expectedSize", + "valueType": "text", + "label": "Expected size" + } + ], + "items": [] + } + }, + "deprecations": { + "id": "deprecations", + "title": "Avoids deprecated APIs", + "description": "Deprecated APIs will eventually be removed from the browser. [Learn more about deprecated APIs](https://developer.chrome.com/docs/lighthouse/best-practices/deprecations/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "value", + "valueType": "text", + "label": "Deprecation / Warning" + }, + { + "key": "source", + "valueType": "source-location", + "label": "Source" + } + ], + "items": [] + } + }, + "third-party-cookies": { + "id": "third-party-cookies", + "title": "Avoids third-party cookies", + "description": "Third-party cookies may be blocked in some contexts. [Learn more about preparing for third-party cookie restrictions](https://privacysandbox.google.com/cookies/prepare/overview).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [] + } + }, + "mainthread-work-breakdown": { + "id": "mainthread-work-breakdown", + "title": "Minimizes main-thread work", + "description": "Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to minimize main-thread work](https://developer.chrome.com/docs/lighthouse/performance/mainthread-work-breakdown/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 587.3279999999983, + "numericUnit": "millisecond", + "displayValue": "0.6 s", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "groupLabel", + "valueType": "text", + "label": "Category" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "group": "styleLayout", + "groupLabel": "Style & Layout", + "duration": 227.05100000000004 + }, + { + "group": "other", + "groupLabel": "Other", + "duration": 226.43199999999803 + }, + { + "group": "scriptEvaluation", + "groupLabel": "Script Evaluation", + "duration": 102.5740000000002 + }, + { + "group": "paintCompositeRender", + "groupLabel": "Rendering", + "duration": 26.605000000000004 + }, + { + "group": "parseHTML", + "groupLabel": "Parse HTML & CSS", + "duration": 2.4949999999999997 + }, + { + "group": "scriptParseCompile", + "groupLabel": "Script Parsing & Compilation", + "duration": 2.1710000000000003 + } + ], + "sortedBy": ["duration"] + }, + "guidanceLevel": 1 + }, + "bootup-time": { + "id": "bootup-time", + "title": "JavaScript execution time", + "description": "Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to reduce Javascript execution time](https://developer.chrome.com/docs/lighthouse/performance/bootup-time/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 103.62200000000016, + "numericUnit": "millisecond", + "displayValue": "0.1 s", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "total", + "granularity": 1, + "valueType": "ms", + "label": "Total CPU Time" + }, + { + "key": "scripting", + "granularity": 1, + "valueType": "ms", + "label": "Script Evaluation" + }, + { + "key": "scriptParseCompile", + "granularity": 1, + "valueType": "ms", + "label": "Script Parse" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "total": 342.96000000000004, + "scripting": 6.939999999999999, + "scriptParseCompile": 1.1869999999999998 + }, + { + "url": "Unattributable", + "total": 140.0000000000005, + "scripting": 1.9829999999999997, + "scriptParseCompile": 0 + }, + { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "total": 100.96600000000016, + "scripting": 92.52800000000016, + "scriptParseCompile": 0.984 + } + ], + "summary": { + "wastedMs": 103.62200000000016 + }, + "sortedBy": ["total"] + }, + "guidanceLevel": 1 + }, + "uses-rel-preconnect": { + "id": "uses-rel-preconnect", + "title": "Preconnect to required origins", + "description": "Consider adding `preconnect` or `dns-prefetch` resource hints to establish early connections to important third-party origins. [Learn how to preconnect to required origins](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "warnings": [], + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0, + "sortedBy": ["wastedMs"] + }, + "guidanceLevel": 3 + }, + "font-display": { + "id": "font-display", + "title": "All text remains visible during webfont loads", + "description": "Leverage the `font-display` CSS feature to ensure text is user-visible while webfonts are loading. [Learn more about `font-display`](https://developer.chrome.com/docs/lighthouse/performance/font-display/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "diagnostics": { + "id": "diagnostics", + "title": "Diagnostics", + "description": "Collection of useful page vitals.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "debugdata", + "items": [ + { + "numRequests": 15, + "numScripts": 2, + "numStylesheets": 1, + "numFonts": 4, + "numTasks": 710, + "numTasksOver10ms": 14, + "numTasksOver25ms": 6, + "numTasksOver50ms": 1, + "numTasksOver100ms": 1, + "numTasksOver500ms": 0, + "rtt": 0.03795, + "throughput": 27920708.39958931, + "maxRtt": 0.03795, + "maxServerLatency": 20.591050000000003, + "totalByteWeight": 363395, + "totalTaskTime": 587.3279999999999, + "mainDocumentTransferSize": 1284 + } + ] + } + }, + "network-requests": { + "id": "network-requests", + "title": "Network Requests", + "description": "Lists the network requests that were made during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + }, + { + "key": "networkRequestTime", + "valueType": "ms", + "granularity": 1, + "label": "Network Request Time" + }, + { + "key": "networkEndTime", + "valueType": "ms", + "granularity": 1, + "label": "Network End Time" + }, + { + "key": "transferSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Transfer Size" + }, + { + "key": "resourceSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Resource Size" + }, + { + "key": "statusCode", + "valueType": "text", + "label": "Status Code" + }, + { + "key": "mimeType", + "valueType": "text", + "label": "MIME Type" + }, + { + "key": "resourceType", + "valueType": "text", + "label": "Resource Type" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 0, + "networkRequestTime": 5.867999996989965, + "networkEndTime": 21.771000001579523, + "finished": true, + "transferSize": 1284, + "resourceSize": 2173, + "statusCode": 200, + "mimeType": "text/html", + "resourceType": "Document", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 43.49399999901652, + "networkRequestTime": 74.39200000092387, + "networkEndTime": 196.96400000154972, + "finished": true, + "transferSize": 116210, + "resourceSize": 371869, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 44.01899999752641, + "networkRequestTime": 81.12099999934435, + "networkEndTime": 130.257999997586, + "finished": true, + "transferSize": 11584, + "resourceSize": 66490, + "statusCode": 200, + "mimeType": "text/css", + "resourceType": "Stylesheet", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 44.25, + "networkRequestTime": 44.25, + "networkEndTime": 150.9700000025332, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "Script", + "priority": "Low", + "experimentalFromMainFrame": true, + "entity": "vlibras.gov.br" + }, + { + "url": "http://localhost:4173/site.webmanifest", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 93.16600000113249, + "networkRequestTime": 96.38000000268221, + "networkEndTime": 138.71900000050664, + "finished": true, + "transferSize": 796, + "resourceSize": 517, + "statusCode": 200, + "mimeType": "application/manifest+json", + "resourceType": "Manifest", + "priority": "Medium", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 278.07699999958277, + "networkRequestTime": 278.81700000166893, + "networkEndTime": 296.1119999997318, + "finished": true, + "transferSize": 22614, + "resourceSize": 116344, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "data:image/svg+xml,%3csvg%20id='Camada_1'%20data-name='Camada%201'%20xmlns='http://www.w3.org/2000/…", + "sessionTargetType": "page", + "protocol": "data", + "rendererStartTime": 281.7179999984801, + "networkRequestTime": 281.7179999984801, + "networkEndTime": 282.3530000001192, + "finished": true, + "transferSize": 0, + "resourceSize": 1581, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "Low", + "experimentalFromMainFrame": true + }, + { + "url": "http://localhost:4173/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 294.8090000003576, + "networkRequestTime": 486.30600000172853, + "networkEndTime": 506.84699999913573, + "finished": true, + "transferSize": 58404, + "resourceSize": 58084, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/inter-latin-wght-normal-Dx4kXJAl.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 330.36600000038743, + "networkRequestTime": 488.6840000003576, + "networkEndTime": 507.9969999976456, + "finished": true, + "transferSize": 48576, + "resourceSize": 48256, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/montserrat-latin-wght-normal-l_AIctKy.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 344.22399999946356, + "networkRequestTime": 488.94399999827147, + "networkEndTime": 523.2659999988973, + "finished": true, + "transferSize": 38276, + "resourceSize": 37956, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 344.4599999971688, + "networkRequestTime": 489.36000000312924, + "networkEndTime": 525.0580000020564, + "finished": true, + "transferSize": 64840, + "resourceSize": 64520, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 559.5610000006855, + "networkRequestTime": 559.5610000006855, + "networkEndTime": 605.8959999978542, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/auth/me/", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 563.0599999986589, + "networkRequestTime": 563.0599999986589, + "networkEndTime": 606.5520000010729, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 601.7259999997914, + "networkRequestTime": 602.4580000005662, + "networkEndTime": 628.5430000014603, + "finished": true, + "transferSize": 684, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 613.2530000023544, + "networkRequestTime": 615.4550000019372, + "networkEndTime": 633.3669999986887, + "finished": true, + "transferSize": 127, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + } + ], + "debugData": { + "type": "debugdata", + "networkStartTimeTs": 27710379261, + "initiators": [ + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 24, + "columnNumber": 70 + }, + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 25, + "columnNumber": 73 + }, + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 39, + "columnNumber": 69 + }, + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 45, + "columnNumber": 13 + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + } + ] + } + } + }, + "network-rtt": { + "id": "network-rtt", + "title": "Network Round Trip Times", + "description": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more about the Round Trip Time](https://hpbn.co/primer-on-latency-and-bandwidth/).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 0.03795, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "rtt", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:4173", + "rtt": 0.03795 + } + ], + "sortedBy": ["rtt"] + } + }, + "network-server-latency": { + "id": "network-server-latency", + "title": "Server Backend Latencies", + "description": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more about server response time](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 20.591050000000003, + "numericUnit": "millisecond", + "displayValue": "20 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "serverResponseTime", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:4173", + "serverResponseTime": 20.591050000000003 + } + ], + "sortedBy": ["serverResponseTime"] + } + }, + "main-thread-tasks": { + "id": "main-thread-tasks", + "title": "Tasks", + "description": "Lists the toplevel main thread tasks that executed during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "End Time" + } + ], + "items": [ + { + "duration": 47.415, + "startTime": 38.139 + }, + { + "duration": 5.516, + "startTime": 87.75 + }, + { + "duration": 19.937, + "startTime": 214.777 + }, + { + "duration": 14.262, + "startTime": 234.725 + }, + { + "duration": 40.009, + "startTime": 251.192 + }, + { + "duration": 169.978, + "startTime": 291.372 + }, + { + "duration": 31.224, + "startTime": 461.37 + }, + { + "duration": 37.051, + "startTime": 493.676 + }, + { + "duration": 7.853, + "startTime": 539.818 + }, + { + "duration": 5.256, + "startTime": 548.008 + }, + { + "duration": 19.775, + "startTime": 553.274 + }, + { + "duration": 16.903, + "startTime": 577.254 + }, + { + "duration": 13.005, + "startTime": 594.905 + }, + { + "duration": 11.674, + "startTime": 621.989 + }, + { + "duration": 41.737, + "startTime": 634.205 + }, + { + "duration": 20.529, + "startTime": 675.955 + }, + { + "duration": 11.817, + "startTime": 697.243 + }, + { + "duration": 5.136, + "startTime": 1267.308 + } + ] + } + }, + "metrics": { + "id": "metrics", + "title": "Metrics", + "description": "Collects all available metrics.", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 749, + "numericUnit": "millisecond", + "details": { + "type": "debugdata", + "items": [ + { + "firstContentfulPaint": 648, + "largestContentfulPaint": 749, + "interactive": 749, + "speedIndex": 648, + "totalBlockingTime": 0, + "maxPotentialFID": 20, + "cumulativeLayoutShift": 0.15287, + "cumulativeLayoutShiftMainFrame": 0.15287, + "timeToFirstByte": 141, + "observedTimeOrigin": 0, + "observedTimeOriginTs": 27710372153, + "observedNavigationStart": 0, + "observedNavigationStartTs": 27710372153, + "observedFirstPaint": 170, + "observedFirstPaintTs": 27710541660, + "observedFirstContentfulPaint": 574, + "observedFirstContentfulPaintTs": 27710945844, + "observedFirstContentfulPaintAllFrames": 574, + "observedFirstContentfulPaintAllFramesTs": 27710945844, + "observedLargestContentfulPaint": 574, + "observedLargestContentfulPaintTs": 27710945844, + "observedLargestContentfulPaintAllFrames": 574, + "observedLargestContentfulPaintAllFramesTs": 27710945844, + "observedTraceEnd": 3047, + "observedTraceEndTs": 27713419495, + "observedLoad": 249, + "observedLoadTs": 27710621628, + "observedDomContentLoaded": 249, + "observedDomContentLoadedTs": 27710621469, + "observedCumulativeLayoutShift": 0.15287, + "observedCumulativeLayoutShiftMainFrame": 0.15287, + "observedFirstVisualChange": 168, + "observedFirstVisualChangeTs": 27710540153, + "observedLastVisualChange": 685, + "observedLastVisualChangeTs": 27711057153, + "observedSpeedIndex": 397, + "observedSpeedIndexTs": 27710768900 + }, + { + "lcpInvalidated": false + } + ] + } + }, + "resource-summary": { + "id": "resource-summary", + "title": "Resources Summary", + "description": "Aggregates all network requests and groups them by type", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Resource Type" + }, + { + "key": "requestCount", + "valueType": "numeric", + "label": "Requests" + }, + { + "key": "transferSize", + "valueType": "bytes", + "label": "Transfer Size" + } + ], + "items": [ + { + "resourceType": "total", + "label": "Total", + "requestCount": 14, + "transferSize": 363395 + }, + { + "resourceType": "font", + "label": "Font", + "requestCount": 4, + "transferSize": 210096 + }, + { + "resourceType": "script", + "label": "Script", + "requestCount": 2, + "transferSize": 116210 + }, + { + "resourceType": "image", + "label": "Image", + "requestCount": 1, + "transferSize": 22614 + }, + { + "resourceType": "stylesheet", + "label": "Stylesheet", + "requestCount": 1, + "transferSize": 11584 + }, + { + "resourceType": "other", + "label": "Other", + "requestCount": 5, + "transferSize": 1607 + }, + { + "resourceType": "document", + "label": "Document", + "requestCount": 1, + "transferSize": 1284 + }, + { + "resourceType": "media", + "label": "Media", + "requestCount": 0, + "transferSize": 0 + }, + { + "resourceType": "third-party", + "label": "Third-party", + "requestCount": 1, + "transferSize": 0 + } + ] + } + }, + "third-party-summary": { + "id": "third-party-summary", + "title": "Minimize third-party usage", + "description": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn how to minimize third-party impact](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "Third-party code blocked the main thread for 0 ms", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "Third-Party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer Size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "blockingTime", + "granularity": 1, + "valueType": "ms", + "label": "Main-Thread Blocking Time", + "subItemsHeading": { + "key": "blockingTime" + } + } + ], + "items": [ + { + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0, + "entity": "vlibras.gov.br", + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0 + } + ] + } + } + ], + "isEntityGrouped": true + }, + "guidanceLevel": 1 + }, + "third-party-facades": { + "id": "third-party-facades", + "title": "Lazy load third-party resources with facades", + "description": "Some third-party embeds can be lazy loaded. Consider replacing them with a facade until they are required. [Learn how to defer third-parties with a facade](https://developer.chrome.com/docs/lighthouse/performance/third-party-facades/).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "TBT": 0 + }, + "guidanceLevel": 3 + }, + "largest-contentful-paint-element": { + "id": "largest-contentful-paint-element", + "title": "Largest Contentful Paint element", + "description": "This is the largest contentful element painted within the viewport. [Learn more about the Largest Contentful Paint element](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "750 ms", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "Element" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 152, + "bottom": 396, + "left": 92, + "right": 686, + "width": 595, + "height": 244 + }, + "snippet": "

      ", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + } + ] + }, + { + "type": "table", + "headings": [ + { + "key": "phase", + "valueType": "text", + "label": "Phase" + }, + { + "key": "percent", + "valueType": "text", + "label": "% of LCP" + }, + { + "key": "timing", + "valueType": "ms", + "label": "Timing" + } + ], + "items": [ + { + "phase": "TTFB", + "timing": 140.59105, + "percent": "19%" + }, + { + "phase": "Load Delay", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Load Time", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Render Delay", + "timing": 608.1821, + "percent": "81%" + } + ] + } + ] + }, + "guidanceLevel": 1 + }, + "lcp-lazy-loaded": { + "id": "lcp-lazy-loaded", + "title": "Largest Contentful Paint image was not lazily loaded", + "description": "Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. [Learn more about optimal lazy loading](https://web.dev/articles/lcp-lazy-loading).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 3 + }, + "layout-shifts": { + "id": "layout-shifts", + "title": "Avoid large layout shifts", + "description": "These are the largest layout shifts observed on the page. Each table item represents a single layout shift, and shows the element that shifted the most. Below each item are possible root causes that led to the layout shift. Some of these layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "2 layout shifts found", + "metricSavings": { + "CLS": 0.153 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-1-SECTION", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION", + "selector": "div#root > div.page-layout > main#main > section.home-news", + "boundingRect": { + "top": 539, + "bottom": 910, + "left": 0, + "right": 1335, + "width": 1335, + "height": 372 + }, + "snippet": "
      ", + "nodeLabel": "Últimas Notícias\nVer todas →\n\nNenhum artigo disponível no momento.\n\nVer todas a…" + }, + "score": 0.145676 + }, + { + "node": { + "type": "node", + "lhId": "page-4-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION,0,DIV,2,DIV", + "selector": "main#main > section.home-news > div.container > div.home-load-more", + "boundingRect": { + "top": 798, + "bottom": 846, + "left": 92, + "right": 1244, + "width": 1152, + "height": 48 + }, + "snippet": "
      ", + "nodeLabel": "Ver todas as notícias" + }, + "score": 0.007194, + "subItems": { + "type": "subitems", + "items": [ + { + "extra": { + "type": "node", + "lhId": "page-6-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1242, + "bottom": 1282, + "left": 92, + "right": 229, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + }, + "cause": "Media element lacking an explicit size" + } + ] + } + } + ] + }, + "guidanceLevel": 2 + }, + "long-tasks": { + "id": "long-tasks", + "title": "Avoid long main-thread tasks", + "description": "Lists the longest tasks on the main thread, useful for identifying worst contributors to input delay. [Learn how to avoid long main-thread tasks](https://web.dev/articles/optimize-long-tasks)", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "1 long task found", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Duration" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "duration": 85, + "startTime": 250.59105 + } + ], + "sortedBy": ["duration"], + "skipSumming": ["startTime"], + "debugData": { + "type": "debugdata", + "urls": ["http://localhost:4173/"], + "tasks": [ + { + "urlIndex": 0, + "startTime": 250.6, + "duration": 85, + "other": 85, + "styleLayout": 0 + } + ] + } + }, + "guidanceLevel": 1 + }, + "non-composited-animations": { + "id": "non-composited-animations", + "title": "Avoid non-composited animations", + "description": "Animations which are not composited can be janky and increase CLS. [Learn how to avoid non-composited animations](https://developer.chrome.com/docs/lighthouse/performance/non-composited-animations/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "failureReason", + "valueType": "text" + }, + "label": "Element" + } + ], + "items": [] + }, + "guidanceLevel": 2 + }, + "unsized-images": { + "id": "unsized-images", + "title": "Image elements do not have explicit `width` and `height`", + "description": "Set an explicit width and height on image elements to reduce layout shifts and improve CLS. [Learn how to set image dimensions](https://web.dev/articles/optimize-cls#images_without_dimensions)", + "score": 0.5, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [ + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "node": { + "type": "node", + "lhId": "1-27-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,1,HEADER,0,DIV,0,A,0,IMG", + "selector": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img", + "boundingRect": { + "top": 13, + "bottom": 51, + "left": 92, + "right": 222, + "width": 130, + "height": 38 + }, + "snippet": "\"\"", + "nodeLabel": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img" + } + }, + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "node": { + "type": "node", + "lhId": "1-28-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV,1,IMG", + "selector": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark", + "boundingRect": { + "top": 262, + "bottom": 348, + "left": 838, + "right": 1132, + "width": 295, + "height": 86 + }, + "snippet": "\"\"", + "nodeLabel": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark" + } + }, + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "node": { + "type": "node", + "lhId": "1-29-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1242, + "bottom": 1282, + "left": 92, + "right": 229, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + } + } + ] + }, + "guidanceLevel": 4 + }, + "valid-source-maps": { + "id": "valid-source-maps", + "title": "Page has valid source maps", + "description": "Source maps translate minified code to the original source code. This helps developers debug in production. In addition, Lighthouse is able to provide further insights. Consider deploying source maps to take advantage of these benefits. [Learn more about source maps](https://developer.chrome.com/docs/devtools/javascript/source-maps/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "scriptUrl", + "valueType": "url", + "subItemsHeading": { + "key": "error" + }, + "label": "URL" + }, + { + "key": "sourceMapUrl", + "valueType": "url", + "label": "Map URL" + } + ], + "items": [] + } + }, + "prioritize-lcp-image": { + "id": "prioritize-lcp-image", + "title": "Preload Largest Contentful Paint image", + "description": "If the LCP element is dynamically added to the page, you should preload the image in order to improve LCP. [Learn more about preloading LCP elements](https://web.dev/articles/optimize-lcp#optimize_when_the_resource_is_discovered).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 4 + }, + "csp-xss": { + "id": "csp-xss", + "title": "Ensure CSP is effective against XSS attacks", + "description": "A strong Content Security Policy (CSP) significantly reduces the risk of cross-site scripting (XSS) attacks. [Learn how to use a CSP to prevent XSS](https://developer.chrome.com/docs/lighthouse/best-practices/csp-xss/)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No CSP found in enforcement mode" + } + ] + } + }, + "has-hsts": { + "id": "has-hsts", + "title": "Use a strong HSTS policy", + "description": "Deployment of the HSTS header significantly reduces the risk of downgrading HTTP connections and eavesdropping attacks. A rollout in stages, starting with a low max-age is recommended. [Learn more about using a strong HSTS policy.](https://developer.chrome.com/docs/lighthouse/best-practices/has-hsts)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No HSTS header found" + } + ] + } + }, + "origin-isolation": { + "id": "origin-isolation", + "title": "Ensure proper origin isolation with COOP", + "description": "The Cross-Origin-Opener-Policy (COOP) can be used to isolate the top-level window from other documents such as pop-ups. [Learn more about deploying the COOP header.](https://web.dev/articles/why-coop-coep#coop)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "description": "No COOP header found", + "severity": "High" + } + ] + } + }, + "clickjacking-mitigation": { + "id": "clickjacking-mitigation", + "title": "Mitigate clickjacking with XFO or CSP", + "description": "The `X-Frame-Options` (XFO) header or the `frame-ancestors` directive in the `Content-Security-Policy` (CSP) header control where a page can be embedded. These can mitigate clickjacking attacks by blocking some or all sites from embedding the page. [Learn more about mitigating clickjacking](https://developer.chrome.com/docs/lighthouse/best-practices/clickjacking-mitigation).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No frame control policy found" + } + ] + } + }, + "trusted-types-xss": { + "id": "trusted-types-xss", + "title": "Mitigate DOM-based XSS with Trusted Types", + "description": "The `require-trusted-types-for` directive in the `Content-Security-Policy` (CSP) header instructs user agents to control the data passed to DOM XSS sink functions. [Learn more about mitigating DOM-based XSS with Trusted Types](https://developer.chrome.com/docs/lighthouse/best-practices/trusted-types-xss).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No `Content-Security-Policy` header with Trusted Types directive found" + } + ] + } + }, + "script-treemap-data": { + "id": "script-treemap-data", + "title": "Script Treemap Data", + "description": "Used for treemap app", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "treemap-data", + "nodes": [ + { + "name": "http://localhost:4173/", + "resourceBytes": 168, + "encodedBytes": 79, + "children": [ + { + "name": "(inline) window.addEvent…", + "resourceBytes": 168, + "unusedBytes": 0 + } + ] + }, + { + "name": "http://localhost:4173/assets/index-BXwNXqtB.js", + "resourceBytes": 371087, + "encodedBytes": 115855, + "unusedBytes": 197160 + } + ] + } + }, + "accesskeys": { + "id": "accesskeys", + "title": "`[accesskey]` values are unique", + "description": "Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more about access keys](https://dequeuniversity.com/rules/axe/4.10/accesskeys).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-allowed-attr": { + "id": "aria-allowed-attr", + "title": "`[aria-*]` attributes match their roles", + "description": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn how to match ARIA attributes to their roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-allowed-role": { + "id": "aria-allowed-role", + "title": "Uses ARIA roles only on compatible elements", + "description": "Many HTML elements can only be assigned certain ARIA roles. Using ARIA roles where they are not allowed can interfere with the accessibility of the web page. [Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-command-name": { + "id": "aria-command-name", + "title": "`button`, `link`, and `menuitem` elements have accessible names", + "description": "When an element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to make command elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-command-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-conditional-attr": { + "id": "aria-conditional-attr", + "title": "ARIA attributes are used as specified for the element's role", + "description": "Some ARIA attributes are only allowed on an element under certain conditions. [Learn more about conditional ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-conditional-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-deprecated-role": { + "id": "aria-deprecated-role", + "title": "Deprecated ARIA roles were not used", + "description": "Deprecated ARIA roles may not be processed correctly by assistive technology. [Learn more about deprecated ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-deprecated-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-dialog-name": { + "id": "aria-dialog-name", + "title": "Elements with `role=\"dialog\"` or `role=\"alertdialog\"` have accessible names.", + "description": "ARIA dialog elements without accessible names may prevent screen readers users from discerning the purpose of these elements. [Learn how to make ARIA dialog elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-dialog-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-hidden-body": { + "id": "aria-hidden-body", + "title": "`[aria-hidden=\"true\"]` is not present on the document ``", + "description": "Assistive technologies, like screen readers, work inconsistently when `aria-hidden=\"true\"` is set on the document ``. [Learn how `aria-hidden` affects the document body](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-body).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-hidden-focus": { + "id": "aria-hidden-focus", + "title": "`[aria-hidden=\"true\"]` elements do not contain focusable descendents", + "description": "Focusable descendents within an `[aria-hidden=\"true\"]` element prevent those interactive elements from being available to users of assistive technologies like screen readers. [Learn how `aria-hidden` affects focusable elements](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-input-field-name": { + "id": "aria-input-field-name", + "title": "ARIA input fields have accessible names", + "description": "When an input field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about input field labels](https://dequeuniversity.com/rules/axe/4.10/aria-input-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-meter-name": { + "id": "aria-meter-name", + "title": "ARIA `meter` elements have accessible names", + "description": "When a meter element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `meter` elements](https://dequeuniversity.com/rules/axe/4.10/aria-meter-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-progressbar-name": { + "id": "aria-progressbar-name", + "title": "ARIA `progressbar` elements have accessible names", + "description": "When a `progressbar` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to label `progressbar` elements](https://dequeuniversity.com/rules/axe/4.10/aria-progressbar-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-prohibited-attr": { + "id": "aria-prohibited-attr", + "title": "Elements use only permitted ARIA attributes", + "description": "Using ARIA attributes in roles where they are prohibited can mean that important information is not communicated to users of assistive technologies. [Learn more about prohibited ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-prohibited-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-attr": { + "id": "aria-required-attr", + "title": "`[role]`s have all required `[aria-*]` attributes", + "description": "Some ARIA roles have required attributes that describe the state of the element to screen readers. [Learn more about roles and required attributes](https://dequeuniversity.com/rules/axe/4.10/aria-required-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-children": { + "id": "aria-required-children", + "title": "Elements with an ARIA `[role]` that require children to contain a specific `[role]` have all required children.", + "description": "Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions. [Learn more about roles and required children elements](https://dequeuniversity.com/rules/axe/4.10/aria-required-children).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-required-parent": { + "id": "aria-required-parent", + "title": "`[role]`s are contained by their required parent element", + "description": "Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions. [Learn more about ARIA roles and required parent element](https://dequeuniversity.com/rules/axe/4.10/aria-required-parent).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-roles": { + "id": "aria-roles", + "title": "`[role]` values are valid", + "description": "ARIA roles must have valid values in order to perform their intended accessibility functions. [Learn more about valid ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-roles).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-text": { + "id": "aria-text", + "title": "Elements with the `role=text` attribute do not have focusable descendents.", + "description": "Adding `role=text` around a text node split by markup enables VoiceOver to treat it as one phrase, but the element's focusable descendents will not be announced. [Learn more about the `role=text` attribute](https://dequeuniversity.com/rules/axe/4.10/aria-text).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-toggle-field-name": { + "id": "aria-toggle-field-name", + "title": "ARIA toggle fields have accessible names", + "description": "When a toggle field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about toggle fields](https://dequeuniversity.com/rules/axe/4.10/aria-toggle-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-tooltip-name": { + "id": "aria-tooltip-name", + "title": "ARIA `tooltip` elements have accessible names", + "description": "When a tooltip element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `tooltip` elements](https://dequeuniversity.com/rules/axe/4.10/aria-tooltip-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-treeitem-name": { + "id": "aria-treeitem-name", + "title": "ARIA `treeitem` elements have accessible names", + "description": "When a `treeitem` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about labeling `treeitem` elements](https://dequeuniversity.com/rules/axe/4.10/aria-treeitem-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-valid-attr-value": { + "id": "aria-valid-attr-value", + "title": "`[aria-*]` attributes have valid values", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more about valid values for ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr-value).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-valid-attr": { + "id": "aria-valid-attr", + "title": "`[aria-*]` attributes are valid and not misspelled", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more about valid ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "button-name": { + "id": "button-name", + "title": "Buttons have an accessible name", + "description": "When a button doesn't have an accessible name, screen readers announce it as \"button\", making it unusable for users who rely on screen readers. [Learn how to make buttons more accessible](https://dequeuniversity.com/rules/axe/4.10/button-name).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "bypass": { + "id": "bypass", + "title": "The page contains a heading, skip link, or landmark region", + "description": "Adding ways to bypass repetitive content lets keyboard users navigate the page more efficiently. [Learn more about bypass blocks](https://dequeuniversity.com/rules/axe/4.10/bypass).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "color-contrast": { + "id": "color-contrast", + "title": "Background and foreground colors have a sufficient contrast ratio", + "description": "Low-contrast text is difficult or impossible for many users to read. [Learn how to provide sufficient color contrast](https://dequeuniversity.com/rules/axe/4.10/color-contrast).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "definition-list": { + "id": "definition-list", + "title": "`
      `'s contain only properly-ordered `
      ` and `
      ` groups, `", + "message": "Syntax not understood" + }, + { + "index": "26", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "27", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "28", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "29", + "line": "
      ", + "message": "Syntax not understood" + }, + { + "index": "30", + "line": "
      ", + "message": "Syntax not understood" + }, + { + "index": "31", + "line": "
      ", + "message": "Syntax not understood" + }, + { + "index": "32", + "line": "
      ", + "message": "Syntax not understood" + }, + { + "index": "33", + "line": "
      ", + "message": "Syntax not understood" + }, + { + "index": "34", + "line": "
      ", + "message": "Syntax not understood" + }, + { + "index": "35", + "line": "
      ", + "message": "Syntax not understood" + }, + { + "index": "36", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "37", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "40", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "41", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "47", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "48", + "line": "", + "message": "Syntax not understood" + } + ] + } + }, + "hreflang": { + "id": "hreflang", + "title": "Document has a valid `hreflang`", + "description": "hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more about `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + }, + "label": "" + } + ], + "items": [] + } + }, + "canonical": { + "id": "canonical", + "title": "Document has a valid `rel=canonical`", + "description": "Canonical links suggest which URL to show in search results. [Learn more about canonical links](https://developer.chrome.com/docs/lighthouse/seo/canonical/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "structured-data": { + "id": "structured-data", + "title": "Structured data is valid", + "description": "Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more about Structured Data](https://developer.chrome.com/docs/lighthouse/seo/structured-data/).", + "score": null, + "scoreDisplayMode": "manual" + }, + "bf-cache": { + "id": "bf-cache", + "title": "Page didn't prevent back/forward cache restoration", + "description": "Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://developer.chrome.com/docs/lighthouse/performance/bf-cache/)", + "score": 1, + "scoreDisplayMode": "binary", + "guidanceLevel": 4 + }, + "cache-insight": { + "id": "cache-insight", + "title": "Use efficient cache lifetimes", + "description": "A long cache lifetime can speed up repeat visits to your page. [Learn more](https://web.dev/uses-long-cache-ttl/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Request" + }, + { + "key": "cacheLifetimeMs", + "valueType": "ms", + "label": "Cache TTL", + "displayUnit": "duration" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size", + "displayUnit": "kb", + "granularity": 1 + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["uses-long-cache-ttl"] + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "text", + "value": "Total" + }, + "score": 0.15287 + }, + { + "node": { + "type": "node", + "lhId": "page-1-SECTION", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION", + "selector": "div#root > div.page-layout > main#main > section.home-news", + "boundingRect": { + "top": 539, + "bottom": 910, + "left": 0, + "right": 1335, + "width": 1335, + "height": 372 + }, + "snippet": "
      ", + "nodeLabel": "Últimas Notícias\nVer todas →\n\nNenhum artigo disponível no momento.\n\nVer todas a…" + }, + "score": 0.145676 + }, + { + "node": { + "type": "node", + "lhId": "page-4-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,1,SECTION,0,DIV,2,DIV", + "selector": "main#main > section.home-news > div.container > div.home-load-more", + "boundingRect": { + "top": 798, + "bottom": 846, + "left": 92, + "right": 1244, + "width": 1152, + "height": 48 + }, + "snippet": "
      ", + "nodeLabel": "Ver todas as notícias" + }, + "score": 0.007194, + "subItems": { + "type": "subitems", + "items": [ + { + "extra": { + "type": "node", + "lhId": "page-6-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1242, + "bottom": 1282, + "left": 92, + "right": 229, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + }, + "cause": "Unsized image element" + } + ] + } + } + ] + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": [ + "layout-shifts", + "non-composited-animations", + "unsized-images" + ] + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "checklist", + "items": { + "noRedirects": { + "label": "Avoids redirects", + "value": true + }, + "serverResponseIsFast": { + "label": "Server responds quickly (observed 15 ms)", + "value": true + }, + "usesCompression": { + "label": "Applies text compression", + "value": true + } + }, + "debugData": { + "type": "debugdata", + "redirectDuration": 0, + "serverResponseTime": 15, + "uncompressedResponseBytes": 0, + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "redirects", + "server-response-time", + "uses-text-compression" + ] + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 104 + } + }, + { + "statistic": "Most children", + "node": { + "type": "node", + "lhId": "page-8-UL", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,0,DIV,1,NAV,0,DIV,1,UL", + "selector": "div.footer__top > nav.footer__links > div.footer__col > ul", + "boundingRect": { + "top": 994, + "bottom": 1169, + "left": 609, + "right": 799, + "width": 190, + "height": 176 + }, + "snippet": "
        ", + "nodeLabel": "Música\nModa\nCinema\nLiteratura\nCultura Digital" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 5 + } + }, + { + "statistic": "DOM depth", + "node": { + "type": "node", + "lhId": "page-9-EM", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1,1,EM", + "selector": "div.home-hero__grid > div.home-hero__text > h1#hero-manifesto > em", + "boundingRect": { + "top": 156, + "bottom": 196, + "left": 131, + "right": 263, + "width": 133, + "height": 40 + }, + "snippet": "", + "nodeLabel": "Interpop" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 10 + } + } + ], + "debugData": { + "type": "debugdata", + "totalElements": 104, + "maxChildren": 5, + "maxDepth": 10 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["dom-size"] + }, + "duplicated-javascript-insight": { + "id": "duplicated-javascript-insight", + "title": "Duplicated JavaScript", + "description": "Remove large, duplicate JavaScript modules from bundles to reduce unnecessary bytes consumed by network activity.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "url", + "valueType": "url" + }, + "label": "Source" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "subItemsHeading": { + "key": "sourceTransferBytes" + }, + "granularity": 10, + "label": "Duplicated bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2, + "replacesAudits": ["duplicated-javascript"] + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["font-display"] + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "A forced reflow occurs when JavaScript queries geometric properties (such as offsetWidth) after styles have been invalidated by a change to the DOM state. This can result in poor performance. Learn more about [forced reflows](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and possible mitigations.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "reflowTime", + "valueType": "ms", + "granularity": 1, + "label": "Total reflow time" + } + ], + "items": [] + } + ] + }, + "guidanceLevel": 3 + }, + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + } + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Resource Size" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Est Savings", + "subItemsHeading": { + "key": "wastedBytes", + "valueType": "bytes" + } + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "modern-image-formats", + "uses-optimized-images", + "efficient-animated-content", + "uses-responsive-images" + ] + }, + "inp-breakdown-insight": { + "id": "inp-breakdown-insight", + "title": "INP breakdown", + "description": "Start investigating with the longest subpart. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["work-during-interaction"] + }, + "lcp-breakdown-insight": { + "id": "lcp-breakdown-insight", + "title": "LCP breakdown", + "description": "Each [subpart has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Subpart" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "subpart": "timeToFirstByte", + "label": "Time to first byte", + "duration": 28.1969999961853 + }, + { + "subpart": "elementRenderDelay", + "label": "Element render delay", + "duration": 545.4940000038147 + } + ] + }, + { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 152, + "bottom": 396, + "left": 92, + "right": 686, + "width": 595, + "height": 244 + }, + "snippet": "

        ", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["largest-contentful-paint-element"] + }, + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["prioritize-lcp-image", "lcp-lazy-loaded"] + }, + "legacy-javascript-insight": { + "id": "legacy-javascript-insight", + "title": "Legacy JavaScript", + "description": "Polyfills and transforms enable older browsers to use new JavaScript features. However, many aren't necessary for modern browsers. Consider modifying your JavaScript build process to not transpile [Baseline](https://web.dev/articles/baseline-and-polyfills) features, unless you know you must support older browsers. [Learn why most sites can deploy ES6+ code without transpiling](https://philipwalton.com/articles/the-state-of-es5-on-the-web/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "subItemsHeading": { + "key": "location", + "valueType": "source-location" + }, + "label": "URL" + }, + { + "key": null, + "valueType": "code", + "subItemsHeading": { + "key": "signal" + }, + "label": "" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Wasted bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2 + }, + "modern-http-insight": { + "id": "modern-http-insight", + "title": "Modern HTTP", + "description": "HTTP/2 and HTTP/3 offer many benefits over HTTP/1.1, such as multiplexing. [Learn more about using modern HTTP](https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "network-dependency-tree-insight": { + "id": "network-dependency-tree-insight", + "title": "Network dependency tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "list-section", + "value": { + "type": "network-tree", + "chains": { + "C45C9DBBC5DE2A1F79093E7EDEFBEFC7": { + "url": "http://localhost:4173/", + "navStartToEndTime": 80, + "transferSize": 1284, + "isLongest": true, + "children": { + "187074.2": { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "navStartToEndTime": 207, + "transferSize": 116210, + "isLongest": true, + "children": { + "187074.37": { + "url": "http://localhost:8000/api/v1/auth/me/", + "navStartToEndTime": 607, + "transferSize": 0, + "isLongest": true, + "children": {} + }, + "187074.36": { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "navStartToEndTime": 606, + "transferSize": 0, + "children": {} + } + } + }, + "187074.3": { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "navStartToEndTime": 134, + "transferSize": 11584, + "children": { + "187074.23": { + "url": "http://localhost:4173/assets/montserrat-latin-wght-normal-l_AIctKy.woff2", + "navStartToEndTime": 532, + "transferSize": 38276, + "children": {} + }, + "187074.26": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2", + "navStartToEndTime": 532, + "transferSize": 64840, + "children": {} + }, + "187074.18": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2", + "navStartToEndTime": 529, + "transferSize": 58404, + "children": {} + }, + "187074.15": { + "url": "http://localhost:4173/assets/inter-latin-wght-normal-Dx4kXJAl.woff2", + "navStartToEndTime": 527, + "transferSize": 48576, + "children": {} + } + } + }, + "187074.8": { + "url": "http://localhost:4173/site.webmanifest", + "navStartToEndTime": 142, + "transferSize": 796, + "children": {} + } + } + } + }, + "longestChain": { + "duration": 607 + } + } + }, + { + "type": "list-section", + "title": "Preconnected origins", + "description": "[preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints help the browser establish a connection earlier in the page load, saving time when the first request for that origin is made. The following are the origins that the page preconnected to.", + "value": { + "type": "text", + "value": "no origins were preconnected" + } + }, + { + "type": "list-section", + "title": "Preconnect candidates", + "description": "Add [preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints to your most important origins, but try to use no more than 4.", + "value": { + "type": "text", + "value": "No additional origins are good candidates for preconnecting" + } + } + ] + }, + "guidanceLevel": 1, + "replacesAudits": ["critical-request-chains", "uses-rel-preconnect"] + }, + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources) can move these network requests out of the critical path.", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "Est savings of 240 ms", + "metricSavings": { + "FCP": 250, + "LCP": 250 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Duration" + } + ], + "items": [ + { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "totalBytes": 11584, + "wastedMs": 61 + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["render-blocking-resources"] + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "3rd parties", + "description": "3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript/) to prioritize your page's content.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "3rd party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "mainThreadTime", + "granularity": 1, + "valueType": "ms", + "label": "Main thread time", + "subItemsHeading": { + "key": "mainThreadTime" + } + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["third-party-summary"] + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-7-META", + "path": "1,HTML,0,HEAD,5,META", + "selector": "head > meta", + "boundingRect": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "snippet": "", + "nodeLabel": "head > meta" + } + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["viewport"] + } + }, + "configSettings": { + "output": ["json"], + "maxWaitForFcp": 30000, + "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "formFactor": "desktop", + "throttling": { + "rttMs": 40, + "throughputKbps": 10240, + "requestLatencyMs": 0, + "downloadThroughputKbps": 0, + "uploadThroughputKbps": 0, + "cpuSlowdownMultiplier": 1 + }, + "throttlingMethod": "simulate", + "screenEmulation": { + "mobile": false, + "width": 1350, + "height": 940, + "deviceScaleFactor": 1, + "disabled": false + }, + "emulatedUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", + "auditMode": false, + "gatherMode": false, + "clearStorageTypes": [ + "file_systems", + "shader_cache", + "service_workers", + "cache_storage" + ], + "disableStorageReset": false, + "debugNavigation": false, + "channel": "cli", + "usePassiveGathering": false, + "disableFullPageScreenshot": false, + "skipAboutBlank": false, + "blankPage": "about:blank", + "ignoreStatusCode": false, + "locale": "en-US", + "blockedUrlPatterns": null, + "additionalTraceCategories": null, + "extraHeaders": null, + "precomputedLanternData": null, + "onlyAudits": null, + "onlyCategories": null, + "skipAudits": null + }, + "categories": { + "performance": { + "title": "Performance", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "first-contentful-paint", + "weight": 10, + "group": "metrics", + "acronym": "FCP" + }, + { + "id": "largest-contentful-paint", + "weight": 25, + "group": "metrics", + "acronym": "LCP" + }, + { + "id": "total-blocking-time", + "weight": 30, + "group": "metrics", + "acronym": "TBT" + }, + { + "id": "cumulative-layout-shift", + "weight": 25, + "group": "metrics", + "acronym": "CLS" + }, + { + "id": "speed-index", + "weight": 10, + "group": "metrics", + "acronym": "SI" + }, + { + "id": "cache-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "duplicated-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "inp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "legacy-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "modern-http-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-dependency-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "third-parties-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interactive", + "weight": 0, + "group": "hidden", + "acronym": "TTI" + }, + { + "id": "max-potential-fid", + "weight": 0, + "group": "hidden" + }, + { + "id": "first-meaningful-paint", + "weight": 0, + "acronym": "FMP", + "group": "hidden" + }, + { + "id": "render-blocking-resources", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-responsive-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "offscreen-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-css", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-css-rules", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-optimized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "modern-image-formats", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-text-compression", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-rel-preconnect", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "server-response-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "redirects", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-http2", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "efficient-animated-content", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "duplicated-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "legacy-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "prioritize-lcp-image", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "total-byte-weight", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-long-cache-ttl", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "dom-size", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "critical-request-chains", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "user-timings", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bootup-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "mainthread-work-breakdown", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "font-display", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-summary", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-facades", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "largest-contentful-paint-element", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "lcp-lazy-loaded", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "layout-shifts", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-passive-event-listeners", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "no-document-write", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "long-tasks", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "non-composited-animations", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unsized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "viewport", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bf-cache", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "network-requests", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-rtt", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-server-latency", + "weight": 0, + "group": "hidden" + }, + { + "id": "main-thread-tasks", + "weight": 0, + "group": "hidden" + }, + { + "id": "diagnostics", + "weight": 0, + "group": "hidden" + }, + { + "id": "metrics", + "weight": 0, + "group": "hidden" + }, + { + "id": "screenshot-thumbnails", + "weight": 0, + "group": "hidden" + }, + { + "id": "final-screenshot", + "weight": 0, + "group": "hidden" + }, + { + "id": "script-treemap-data", + "weight": 0, + "group": "hidden" + }, + { + "id": "resource-summary", + "weight": 0, + "group": "hidden" + } + ], + "id": "performance", + "score": 0.93 + }, + "accessibility": { + "title": "Accessibility", + "description": "These checks highlight opportunities to [improve the accessibility of your web app](https://developer.chrome.com/docs/lighthouse/accessibility/). Automatic detection can only detect a subset of issues and does not guarantee the accessibility of your web app, so [manual testing](https://web.dev/articles/how-to-review) is also encouraged.", + "manualDescription": "These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://web.dev/articles/how-to-review).", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "accesskeys", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "aria-allowed-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-allowed-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-command-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-conditional-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-deprecated-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-dialog-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-body", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-focus", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-input-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-meter-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-progressbar-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-prohibited-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-required-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-required-children", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-required-parent", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-roles", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-text", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-toggle-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-tooltip-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-treeitem-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr-value", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "button-name", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "bypass", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "color-contrast", + "weight": 7, + "group": "a11y-color-contrast" + }, + { + "id": "definition-list", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "dlitem", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "document-title", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "duplicate-id-aria", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "form-field-multiple-labels", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "frame-title", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "heading-order", + "weight": 3, + "group": "a11y-navigation" + }, + { + "id": "html-has-lang", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-lang-valid", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-xml-lang-mismatch", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "image-alt", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "image-redundant-alt", + "weight": 1, + "group": "a11y-names-labels" + }, + { + "id": "input-button-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "input-image-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "label", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "link-in-text-block", + "weight": 0, + "group": "a11y-color-contrast" + }, + { + "id": "link-name", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "list", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "listitem", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "meta-refresh", + "weight": 0, + "group": "a11y-best-practices" + }, + { + "id": "meta-viewport", + "weight": 10, + "group": "a11y-best-practices" + }, + { + "id": "object-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "select-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "skip-link", + "weight": 3, + "group": "a11y-names-labels" + }, + { + "id": "tabindex", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "table-duplicate-name", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "target-size", + "weight": 7, + "group": "a11y-best-practices" + }, + { + "id": "td-headers-attr", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "th-has-data-cells", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "valid-lang", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "video-caption", + "weight": 0, + "group": "a11y-audio-video" + }, + { + "id": "focusable-controls", + "weight": 0 + }, + { + "id": "interactive-element-affordance", + "weight": 0 + }, + { + "id": "logical-tab-order", + "weight": 0 + }, + { + "id": "visual-order-follows-dom", + "weight": 0 + }, + { + "id": "focus-traps", + "weight": 0 + }, + { + "id": "managed-focus", + "weight": 0 + }, + { + "id": "use-landmarks", + "weight": 0 + }, + { + "id": "offscreen-content-hidden", + "weight": 0 + }, + { + "id": "custom-controls-labels", + "weight": 0 + }, + { + "id": "custom-controls-roles", + "weight": 0 + }, + { + "id": "empty-heading", + "weight": 0, + "group": "hidden" + }, + { + "id": "identical-links-same-purpose", + "weight": 0, + "group": "hidden" + }, + { + "id": "landmark-one-main", + "weight": 0, + "group": "hidden" + }, + { + "id": "label-content-name-mismatch", + "weight": 0, + "group": "hidden" + }, + { + "id": "table-fake-caption", + "weight": 0, + "group": "hidden" + }, + { + "id": "td-has-header", + "weight": 0, + "group": "hidden" + } + ], + "id": "accessibility", + "score": 1 + }, + "best-practices": { + "title": "Best Practices", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "is-on-https", + "weight": 5, + "group": "best-practices-trust-safety" + }, + { + "id": "redirects-http", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "geolocation-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "notification-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "csp-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "has-hsts", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "origin-isolation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "clickjacking-mitigation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "trusted-types-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "paste-preventing-inputs", + "weight": 3, + "group": "best-practices-ux" + }, + { + "id": "image-aspect-ratio", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "image-size-responsive", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "viewport", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "font-size", + "weight": 0, + "group": "best-practices-ux" + }, + { + "id": "doctype", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "charset", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "js-libraries", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "deprecations", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "third-party-cookies", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "errors-in-console", + "weight": 1, + "group": "best-practices-general" + }, + { + "id": "valid-source-maps", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "inspector-issues", + "weight": 1, + "group": "best-practices-general" + } + ], + "id": "best-practices", + "score": 0.96 + }, + "seo": { + "title": "SEO", + "description": "These checks ensure that your page is following basic search engine optimization advice. There are many additional factors Lighthouse does not score here that may affect your search ranking, including performance on [Core Web Vitals](https://web.dev/explore/vitals). [Learn more about Google Search Essentials](https://support.google.com/webmasters/answer/35769).", + "manualDescription": "Run these additional validators on your site to check additional SEO best practices.", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "is-crawlable", + "weight": 4.043478260869565, + "group": "seo-crawl" + }, + { + "id": "document-title", + "weight": 1, + "group": "seo-content" + }, + { + "id": "meta-description", + "weight": 1, + "group": "seo-content" + }, + { + "id": "http-status-code", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "link-text", + "weight": 1, + "group": "seo-content" + }, + { + "id": "crawlable-anchors", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "robots-txt", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "image-alt", + "weight": 1, + "group": "seo-content" + }, + { + "id": "hreflang", + "weight": 1, + "group": "seo-content" + }, + { + "id": "canonical", + "weight": 0, + "group": "seo-content" + }, + { + "id": "structured-data", + "weight": 0 + } + ], + "id": "seo", + "score": 0.92 + } + }, + "categoryGroups": { + "metrics": { + "title": "Metrics" + }, + "insights": { + "title": "Insights", + "description": "These insights are also available in the Chrome DevTools Performance Panel - [record a trace](https://developer.chrome.com/docs/devtools/performance/reference) to view more detailed information." + }, + "diagnostics": { + "title": "Diagnostics", + "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." + }, + "a11y-best-practices": { + "title": "Best practices", + "description": "These items highlight common accessibility best practices." + }, + "a11y-color-contrast": { + "title": "Contrast", + "description": "These are opportunities to improve the legibility of your content." + }, + "a11y-names-labels": { + "title": "Names and labels", + "description": "These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-navigation": { + "title": "Navigation", + "description": "These are opportunities to improve keyboard navigation in your application." + }, + "a11y-aria": { + "title": "ARIA", + "description": "These are opportunities to improve the usage of ARIA in your application which may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-language": { + "title": "Internationalization and localization", + "description": "These are opportunities to improve the interpretation of your content by users in different locales." + }, + "a11y-audio-video": { + "title": "Audio and video", + "description": "These are opportunities to provide alternative content for audio and video. This may improve the experience for users with hearing or vision impairments." + }, + "a11y-tables-lists": { + "title": "Tables and lists", + "description": "These are opportunities to improve the experience of reading tabular or list data using assistive technology, like a screen reader." + }, + "seo-mobile": { + "title": "Mobile Friendly", + "description": "Make sure your pages are mobile friendly so users don’t have to pinch or zoom in order to read the content pages. [Learn how to make pages mobile-friendly](https://developers.google.com/search/mobile-sites/)." + }, + "seo-content": { + "title": "Content Best Practices", + "description": "Format your HTML in a way that enables crawlers to better understand your app’s content." + }, + "seo-crawl": { + "title": "Crawling and Indexing", + "description": "To appear in search results, crawlers need access to your app." + }, + "best-practices-trust-safety": { + "title": "Trust and Safety" + }, + "best-practices-ux": { + "title": "User Experience" + }, + "best-practices-browser-compat": { + "title": "Browser Compatibility" + }, + "best-practices-general": { + "title": "General" + }, + "hidden": { + "title": "" + } + }, + "stackPacks": [], + "entities": [ + { + "name": "localhost", + "origins": ["http://localhost:4173", "http://localhost:8000"], + "isFirstParty": true, + "isUnrecognized": true + }, + { + "name": "vlibras.gov.br", + "origins": ["https://vlibras.gov.br"], + "isUnrecognized": true + } + ], + "fullPageScreenshot": { + "screenshot": { + "data": "data:image/webp;base64,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", + "width": 765, + "height": 1306 + }, + "nodes": { + "page-0-H1": { + "id": "hero-manifesto", + "top": 136, + "bottom": 239, + "left": 24, + "right": 595, + "width": 571, + "height": 102 + }, + "page-1-SECTION": { + "id": "", + "top": 569, + "bottom": 941, + "left": 0, + "right": 765, + "width": 765, + "height": 372 + }, + "page-2-P": { + "id": "", + "top": 259, + "bottom": 340, + "left": 24, + "right": 549, + "width": 525, + "height": 82 + }, + "page-3-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 193, + "right": 573, + "width": 380, + "height": 111 + }, + "page-4-DIV": { + "id": "", + "top": 829, + "bottom": 877, + "left": 24, + "right": 741, + "width": 717, + "height": 48 + }, + "page-5-FOOTER": { + "id": "", + "top": 941, + "bottom": 1498, + "left": 0, + "right": 765, + "width": 765, + "height": 558 + }, + "page-6-IMG": { + "id": "", + "top": 1434, + "bottom": 1474, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "page-7-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "page-8-UL": { + "id": "", + "top": 1185, + "bottom": 1361, + "left": 24, + "right": 242, + "width": 218, + "height": 176 + }, + "page-9-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "page-10-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-0-A": { + "id": "", + "top": -81, + "bottom": -34, + "left": 12, + "right": 194, + "width": 182, + "height": 46 + }, + "1-1-A": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-2-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-3-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-4-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-5-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-6-A": { + "id": "", + "top": 586, + "bottom": 609, + "left": 651, + "right": 741, + "width": 90, + "height": 22 + }, + "1-7-A": { + "id": "", + "top": 829, + "bottom": 877, + "left": 268, + "right": 497, + "width": 229, + "height": 48 + }, + "1-8-A": { + "id": "", + "top": 1189, + "bottom": 1206, + "left": 24, + "right": 72, + "width": 48, + "height": 17 + }, + "1-9-A": { + "id": "", + "top": 1227, + "bottom": 1244, + "left": 24, + "right": 61, + "width": 37, + "height": 17 + }, + "1-10-A": { + "id": "", + "top": 1265, + "bottom": 1282, + "left": 24, + "right": 74, + "width": 50, + "height": 17 + }, + "1-11-A": { + "id": "", + "top": 1302, + "bottom": 1319, + "left": 24, + "right": 87, + "width": 63, + "height": 17 + }, + "1-12-A": { + "id": "", + "top": 1340, + "bottom": 1357, + "left": 24, + "right": 117, + "width": 93, + "height": 17 + }, + "1-13-A": { + "id": "", + "top": 1189, + "bottom": 1206, + "left": 274, + "right": 341, + "width": 67, + "height": 17 + }, + "1-14-A": { + "id": "", + "top": 1227, + "bottom": 1244, + "left": 274, + "right": 331, + "width": 58, + "height": 17 + }, + "1-15-A": { + "id": "", + "top": 1265, + "bottom": 1282, + "left": 274, + "right": 328, + "width": 54, + "height": 17 + }, + "1-16-A": { + "id": "", + "top": 1302, + "bottom": 1319, + "left": 274, + "right": 326, + "width": 52, + "height": 17 + }, + "1-17-A": { + "id": "", + "top": 1189, + "bottom": 1206, + "left": 523, + "right": 563, + "width": 40, + "height": 17 + }, + "1-18-A": { + "id": "", + "top": 1227, + "bottom": 1244, + "left": 523, + "right": 597, + "width": 73, + "height": 17 + }, + "1-19-A": { + "id": "", + "top": 1265, + "bottom": 1282, + "left": 523, + "right": 636, + "width": 112, + "height": 17 + }, + "1-20-A": { + "id": "", + "top": 1434, + "bottom": 1474, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-21-A": { + "id": "", + "top": 1445, + "bottom": 1464, + "left": 378, + "right": 445, + "width": 67, + "height": 19 + }, + "1-22-A": { + "id": "", + "top": 1445, + "bottom": 1464, + "left": 465, + "right": 549, + "width": 84, + "height": 19 + }, + "1-23-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "1-24-UL": { + "id": "", + "top": 1185, + "bottom": 1361, + "left": 24, + "right": 242, + "width": 218, + "height": 176 + }, + "1-25-FORM": { + "id": "", + "top": 1075, + "bottom": 1118, + "left": 24, + "right": 741, + "width": 717, + "height": 43 + }, + "1-26-INPUT": { + "id": "", + "top": 1075, + "bottom": 1118, + "left": 24, + "right": 626, + "width": 602, + "height": 43 + }, + "1-27-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-28-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 193, + "right": 573, + "width": 380, + "height": 111 + }, + "1-29-IMG": { + "id": "", + "top": 1434, + "bottom": 1474, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-30-IMG": { + "id": "", + "top": 1445, + "bottom": 1463, + "left": 701, + "right": 741, + "width": 40, + "height": 18 + }, + "1-31-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-32-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-33-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-34-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-35-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-36-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-37-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-38-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-39-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-40-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-41-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-42-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + } + } + }, + "timing": { + "entries": [ + { + "startTime": 1733.65, + "name": "lh:config", + "duration": 1225.59, + "entryType": "measure" + }, + { + "startTime": 1738.66, + "name": "lh:config:resolveArtifactsToDefns", + "duration": 100.86, + "entryType": "measure" + }, + { + "startTime": 2959.54, + "name": "lh:runner:gather", + "duration": 8087.26, + "entryType": "measure" + }, + { + "startTime": 3112.5, + "name": "lh:driver:connect", + "duration": 20.86, + "entryType": "measure" + }, + { + "startTime": 3133.74, + "name": "lh:driver:navigate", + "duration": 12.87, + "entryType": "measure" + }, + { + "startTime": 3147.16, + "name": "lh:gather:getBenchmarkIndex", + "duration": 1016.01, + "entryType": "measure" + }, + { + "startTime": 4163.41, + "name": "lh:gather:getVersion", + "duration": 8.01, + "entryType": "measure" + }, + { + "startTime": 4171.59, + "name": "lh:gather:getDevicePixelRatio", + "duration": 1.66, + "entryType": "measure" + }, + { + "startTime": 4173.86, + "name": "lh:prepare:navigationMode", + "duration": 94.37, + "entryType": "measure" + }, + { + "startTime": 4231.31, + "name": "lh:storage:clearDataForOrigin", + "duration": 12.89, + "entryType": "measure" + }, + { + "startTime": 4244.41, + "name": "lh:storage:clearBrowserCaches", + "duration": 14.81, + "entryType": "measure" + }, + { + "startTime": 4262.53, + "name": "lh:gather:prepareThrottlingAndNetwork", + "duration": 5.64, + "entryType": "measure" + }, + { + "startTime": 4370.1, + "name": "lh:driver:navigate", + "duration": 3058.91, + "entryType": "measure" + }, + { + "startTime": 7891.79, + "name": "lh:computed:NetworkRecords", + "duration": 2.61, + "entryType": "measure" + }, + { + "startTime": 7896, + "name": "lh:gather:getArtifact:DevtoolsLog", + "duration": 0.08, + "entryType": "measure" + }, + { + "startTime": 7896.12, + "name": "lh:gather:getArtifact:Trace", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 7896.19, + "name": "lh:gather:getArtifact:Accessibility", + "duration": 474.74, + "entryType": "measure" + }, + { + "startTime": 8370.98, + "name": "lh:gather:getArtifact:AnchorElements", + "duration": 472.93, + "entryType": "measure" + }, + { + "startTime": 8843.96, + "name": "lh:gather:getArtifact:ConsoleMessages", + "duration": 0.07, + "entryType": "measure" + }, + { + "startTime": 8844.05, + "name": "lh:gather:getArtifact:CSSUsage", + "duration": 38.48, + "entryType": "measure" + }, + { + "startTime": 8882.61, + "name": "lh:gather:getArtifact:Doctype", + "duration": 2.49, + "entryType": "measure" + }, + { + "startTime": 8885.14, + "name": "lh:gather:getArtifact:DOMStats", + "duration": 6.56, + "entryType": "measure" + }, + { + "startTime": 8891.74, + "name": "lh:gather:getArtifact:FontSize", + "duration": 14.95, + "entryType": "measure" + }, + { + "startTime": 8906.73, + "name": "lh:gather:getArtifact:Inputs", + "duration": 7.08, + "entryType": "measure" + }, + { + "startTime": 8913.9, + "name": "lh:gather:getArtifact:ImageElements", + "duration": 70.32, + "entryType": "measure" + }, + { + "startTime": 8984.44, + "name": "lh:gather:getArtifact:InspectorIssues", + "duration": 0.59, + "entryType": "measure" + }, + { + "startTime": 8985.18, + "name": "lh:gather:getArtifact:JsUsage", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 8985.61, + "name": "lh:gather:getArtifact:LinkElements", + "duration": 7.04, + "entryType": "measure" + }, + { + "startTime": 8992.29, + "name": "lh:computed:MainResource", + "duration": 0.31, + "entryType": "measure" + }, + { + "startTime": 8992.73, + "name": "lh:gather:getArtifact:MainDocumentContent", + "duration": 3.03, + "entryType": "measure" + }, + { + "startTime": 8995.79, + "name": "lh:gather:getArtifact:MetaElements", + "duration": 6.62, + "entryType": "measure" + }, + { + "startTime": 9002.48, + "name": "lh:gather:getArtifact:NetworkUserAgent", + "duration": 0.2, + "entryType": "measure" + }, + { + "startTime": 9002.72, + "name": "lh:gather:getArtifact:OptimizedImages", + "duration": 0.64, + "entryType": "measure" + }, + { + "startTime": 9003.41, + "name": "lh:gather:getArtifact:ResponseCompression", + "duration": 0.77, + "entryType": "measure" + }, + { + "startTime": 9004.23, + "name": "lh:gather:getArtifact:RobotsTxt", + "duration": 11.49, + "entryType": "measure" + }, + { + "startTime": 9015.77, + "name": "lh:gather:getArtifact:Scripts", + "duration": 0.29, + "entryType": "measure" + }, + { + "startTime": 9016.16, + "name": "lh:gather:getArtifact:SourceMaps", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 9016.44, + "name": "lh:gather:getArtifact:Stacks", + "duration": 33.83, + "entryType": "measure" + }, + { + "startTime": 9016.72, + "name": "lh:gather:collectStacks", + "duration": 33.52, + "entryType": "measure" + }, + { + "startTime": 9050.3, + "name": "lh:gather:getArtifact:Stylesheets", + "duration": 22.75, + "entryType": "measure" + }, + { + "startTime": 9073.16, + "name": "lh:gather:getArtifact:TraceElements", + "duration": 439.42, + "entryType": "measure" + }, + { + "startTime": 9073.78, + "name": "lh:computed:TraceEngineResult", + "duration": 369.32, + "entryType": "measure" + }, + { + "startTime": 9074, + "name": "lh:computed:ProcessedTrace", + "duration": 45.63, + "entryType": "measure" + }, + { + "startTime": 9121.44, + "name": "lh:computed:TraceEngineResult:total", + "duration": 311.47, + "entryType": "measure" + }, + { + "startTime": 9121.47, + "name": "lh:computed:TraceEngineResult:parse", + "duration": 237.49, + "entryType": "measure" + }, + { + "startTime": 9122.74, + "name": "lh:computed:TraceEngineResult:parse:handleEvent", + "duration": 160.59, + "entryType": "measure" + }, + { + "startTime": 9283.38, + "name": "lh:computed:TraceEngineResult:parse:Meta:finalize", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 9285.02, + "name": "lh:computed:TraceEngineResult:parse:AnimationFrames:finalize", + "duration": 1.32, + "entryType": "measure" + }, + { + "startTime": 9286.39, + "name": "lh:computed:TraceEngineResult:parse:Animations:finalize", + "duration": 1.83, + "entryType": "measure" + }, + { + "startTime": 9288.26, + "name": "lh:computed:TraceEngineResult:parse:Samples:finalize", + "duration": 1.32, + "entryType": "measure" + }, + { + "startTime": 9289.63, + "name": "lh:computed:TraceEngineResult:parse:AuctionWorklets:finalize", + "duration": 2.58, + "entryType": "measure" + }, + { + "startTime": 9292.36, + "name": "lh:computed:TraceEngineResult:parse:NetworkRequests:finalize", + "duration": 6.41, + "entryType": "measure" + }, + { + "startTime": 9298.84, + "name": "lh:computed:TraceEngineResult:parse:Renderer:finalize", + "duration": 9.96, + "entryType": "measure" + }, + { + "startTime": 9308.87, + "name": "lh:computed:TraceEngineResult:parse:Flows:finalize", + "duration": 4.03, + "entryType": "measure" + }, + { + "startTime": 9312.96, + "name": "lh:computed:TraceEngineResult:parse:AsyncJSCalls:finalize", + "duration": 2.92, + "entryType": "measure" + }, + { + "startTime": 9316.06, + "name": "lh:computed:TraceEngineResult:parse:DOMStats:finalize", + "duration": 0.23, + "entryType": "measure" + }, + { + "startTime": 9316.37, + "name": "lh:computed:TraceEngineResult:parse:UserTimings:finalize", + "duration": 1.44, + "entryType": "measure" + }, + { + "startTime": 9317.85, + "name": "lh:computed:TraceEngineResult:parse:ExtensionTraceData:finalize", + "duration": 2.91, + "entryType": "measure" + }, + { + "startTime": 9320.8, + "name": "lh:computed:TraceEngineResult:parse:LayerTree:finalize", + "duration": 1.81, + "entryType": "measure" + }, + { + "startTime": 9322.64, + "name": "lh:computed:TraceEngineResult:parse:Frames:finalize", + "duration": 7.19, + "entryType": "measure" + }, + { + "startTime": 9329.87, + "name": "lh:computed:TraceEngineResult:parse:GPU:finalize", + "duration": 1.33, + "entryType": "measure" + }, + { + "startTime": 9331.24, + "name": "lh:computed:TraceEngineResult:parse:ImagePainting:finalize", + "duration": 1.27, + "entryType": "measure" + }, + { + "startTime": 9332.54, + "name": "lh:computed:TraceEngineResult:parse:Initiators:finalize", + "duration": 1.66, + "entryType": "measure" + }, + { + "startTime": 9334.24, + "name": "lh:computed:TraceEngineResult:parse:Invalidations:finalize", + "duration": 1.16, + "entryType": "measure" + }, + { + "startTime": 9335.43, + "name": "lh:computed:TraceEngineResult:parse:PageLoadMetrics:finalize", + "duration": 2.49, + "entryType": "measure" + }, + { + "startTime": 9337.96, + "name": "lh:computed:TraceEngineResult:parse:LargestImagePaint:finalize", + "duration": 1.36, + "entryType": "measure" + }, + { + "startTime": 9339.34, + "name": "lh:computed:TraceEngineResult:parse:LargestTextPaint:finalize", + "duration": 1.17, + "entryType": "measure" + }, + { + "startTime": 9340.55, + "name": "lh:computed:TraceEngineResult:parse:Screenshots:finalize", + "duration": 2.5, + "entryType": "measure" + }, + { + "startTime": 9343.11, + "name": "lh:computed:TraceEngineResult:parse:LayoutShifts:finalize", + "duration": 3.03, + "entryType": "measure" + }, + { + "startTime": 9346.19, + "name": "lh:computed:TraceEngineResult:parse:Memory:finalize", + "duration": 1.26, + "entryType": "measure" + }, + { + "startTime": 9347.51, + "name": "lh:computed:TraceEngineResult:parse:PageFrames:finalize", + "duration": 1.27, + "entryType": "measure" + }, + { + "startTime": 9348.88, + "name": "lh:computed:TraceEngineResult:parse:Scripts:finalize", + "duration": 2.69, + "entryType": "measure" + }, + { + "startTime": 9351.6, + "name": "lh:computed:TraceEngineResult:parse:SelectorStats:finalize", + "duration": 1.22, + "entryType": "measure" + }, + { + "startTime": 9352.85, + "name": "lh:computed:TraceEngineResult:parse:UserInteractions:finalize", + "duration": 1.46, + "entryType": "measure" + }, + { + "startTime": 9354.34, + "name": "lh:computed:TraceEngineResult:parse:Workers:finalize", + "duration": 1.17, + "entryType": "measure" + }, + { + "startTime": 9355.54, + "name": "lh:computed:TraceEngineResult:parse:Warnings:finalize", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 9357.03, + "name": "lh:computed:TraceEngineResult:parse:clone", + "duration": 1.83, + "entryType": "measure" + }, + { + "startTime": 9358.97, + "name": "lh:computed:TraceEngineResult:insights", + "duration": 73.91, + "entryType": "measure" + }, + { + "startTime": 9359.6, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 1.63, + "entryType": "measure" + }, + { + "startTime": 9361.27, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 9361.62, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 0.66, + "entryType": "measure" + }, + { + "startTime": 9362.29, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 9362.54, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 3.14, + "entryType": "measure" + }, + { + "startTime": 9365.76, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.51, + "entryType": "measure" + }, + { + "startTime": 9366.3, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.3, + "entryType": "measure" + }, + { + "startTime": 9366.62, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.19, + "entryType": "measure" + }, + { + "startTime": 9366.85, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.54, + "entryType": "measure" + }, + { + "startTime": 9367.41, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 9367.6, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 0.22, + "entryType": "measure" + }, + { + "startTime": 9367.83, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 9368.1, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 9368.44, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 0.13, + "entryType": "measure" + }, + { + "startTime": 9368.6, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 0.19, + "entryType": "measure" + }, + { + "startTime": 9368.8, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 9369.08, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 1.8, + "entryType": "measure" + }, + { + "startTime": 9370.9, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 9371.45, + "name": "lh:computed:TraceEngineResult:insights:createLanternContext", + "duration": 31.89, + "entryType": "measure" + }, + { + "startTime": 9403.43, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 1, + "entryType": "measure" + }, + { + "startTime": 9404.45, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 0.53, + "entryType": "measure" + }, + { + "startTime": 9405, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 0.19, + "entryType": "measure" + }, + { + "startTime": 9405.2, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.38, + "entryType": "measure" + }, + { + "startTime": 9405.59, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 0.35, + "entryType": "measure" + }, + { + "startTime": 9405.96, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.13, + "entryType": "measure" + }, + { + "startTime": 9406.12, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.03, + "entryType": "measure" + }, + { + "startTime": 9406.17, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 9406.23, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.35, + "entryType": "measure" + }, + { + "startTime": 9406.6, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.39, + "entryType": "measure" + }, + { + "startTime": 9407.02, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 0.05, + "entryType": "measure" + }, + { + "startTime": 9407.09, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 9.26, + "entryType": "measure" + }, + { + "startTime": 9416.38, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 2.5, + "entryType": "measure" + }, + { + "startTime": 9418.9, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 2, + "entryType": "measure" + }, + { + "startTime": 9420.92, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 0.92, + "entryType": "measure" + }, + { + "startTime": 9421.86, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 9421.91, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 9.59, + "entryType": "measure" + }, + { + "startTime": 9431.54, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.07, + "entryType": "measure" + }, + { + "startTime": 9446.55, + "name": "lh:computed:ProcessedNavigation", + "duration": 1.09, + "entryType": "measure" + }, + { + "startTime": 9447.77, + "name": "lh:computed:CumulativeLayoutShift", + "duration": 11.42, + "entryType": "measure" + }, + { + "startTime": 9460.12, + "name": "lh:computed:Responsiveness", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 9512.61, + "name": "lh:gather:getArtifact:ViewportDimensions", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 9514.1, + "name": "lh:gather:getArtifact:FullPageScreenshot", + "duration": 1183.3, + "entryType": "measure" + }, + { + "startTime": 10697.51, + "name": "lh:gather:getArtifact:BFCacheFailures", + "duration": 261.22, + "entryType": "measure" + }, + { + "startTime": 11048.21, + "name": "lh:runner:audit", + "duration": 812.01, + "entryType": "measure" + }, + { + "startTime": 11048.72, + "name": "lh:runner:auditing", + "duration": 810.84, + "entryType": "measure" + }, + { + "startTime": 11052.83, + "name": "lh:audit:is-on-https", + "duration": 7.09, + "entryType": "measure" + }, + { + "startTime": 11061.56, + "name": "lh:audit:redirects-http", + "duration": 3.37, + "entryType": "measure" + }, + { + "startTime": 11066.66, + "name": "lh:audit:viewport", + "duration": 5.91, + "entryType": "measure" + }, + { + "startTime": 11068.29, + "name": "lh:computed:ViewportMeta", + "duration": 1.57, + "entryType": "measure" + }, + { + "startTime": 11073.53, + "name": "lh:audit:first-contentful-paint", + "duration": 20.97, + "entryType": "measure" + }, + { + "startTime": 11075.41, + "name": "lh:computed:FirstContentfulPaint", + "duration": 14.42, + "entryType": "measure" + }, + { + "startTime": 11077.01, + "name": "lh:computed:LanternFirstContentfulPaint", + "duration": 12.79, + "entryType": "measure" + }, + { + "startTime": 11077.62, + "name": "lh:computed:PageDependencyGraph", + "duration": 8.29, + "entryType": "measure" + }, + { + "startTime": 11086.01, + "name": "lh:computed:LoadSimulator", + "duration": 1.23, + "entryType": "measure" + }, + { + "startTime": 11086.22, + "name": "lh:computed:NetworkAnalysis", + "duration": 0.88, + "entryType": "measure" + }, + { + "startTime": 11095.01, + "name": "lh:audit:largest-contentful-paint", + "duration": 6.51, + "entryType": "measure" + }, + { + "startTime": 11095.94, + "name": "lh:computed:LargestContentfulPaint", + "duration": 3.66, + "entryType": "measure" + }, + { + "startTime": 11096.08, + "name": "lh:computed:LanternLargestContentfulPaint", + "duration": 3.48, + "entryType": "measure" + }, + { + "startTime": 11101.9, + "name": "lh:audit:first-meaningful-paint", + "duration": 1.19, + "entryType": "measure" + }, + { + "startTime": 11103.6, + "name": "lh:audit:speed-index", + "duration": 157.77, + "entryType": "measure" + }, + { + "startTime": 11104.15, + "name": "lh:computed:SpeedIndex", + "duration": 156.18, + "entryType": "measure" + }, + { + "startTime": 11104.31, + "name": "lh:computed:LanternSpeedIndex", + "duration": 155.99, + "entryType": "measure" + }, + { + "startTime": 11104.4, + "name": "lh:computed:Speedline", + "duration": 151.89, + "entryType": "measure" + }, + { + "startTime": 11261.43, + "name": "lh:audit:screenshot-thumbnails", + "duration": 0.99, + "entryType": "measure" + }, + { + "startTime": 11262.53, + "name": "lh:audit:final-screenshot", + "duration": 1.41, + "entryType": "measure" + }, + { + "startTime": 11262.77, + "name": "lh:computed:Screenshots", + "duration": 1.01, + "entryType": "measure" + }, + { + "startTime": 11264.58, + "name": "lh:audit:total-blocking-time", + "duration": 8.23, + "entryType": "measure" + }, + { + "startTime": 11265.84, + "name": "lh:computed:TotalBlockingTime", + "duration": 5.9, + "entryType": "measure" + }, + { + "startTime": 11266.07, + "name": "lh:computed:LanternTotalBlockingTime", + "duration": 5.64, + "entryType": "measure" + }, + { + "startTime": 11266.51, + "name": "lh:computed:LanternInteractive", + "duration": 2.77, + "entryType": "measure" + }, + { + "startTime": 11273.05, + "name": "lh:audit:max-potential-fid", + "duration": 5.66, + "entryType": "measure" + }, + { + "startTime": 11273.52, + "name": "lh:computed:MaxPotentialFID", + "duration": 3.7, + "entryType": "measure" + }, + { + "startTime": 11273.68, + "name": "lh:computed:LanternMaxPotentialFID", + "duration": 3.48, + "entryType": "measure" + }, + { + "startTime": 11279.03, + "name": "lh:audit:cumulative-layout-shift", + "duration": 1.05, + "entryType": "measure" + }, + { + "startTime": 11280.53, + "name": "lh:audit:errors-in-console", + "duration": 3.13, + "entryType": "measure" + }, + { + "startTime": 11281.26, + "name": "lh:computed:JSBundles", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 11283.95, + "name": "lh:audit:server-response-time", + "duration": 1.73, + "entryType": "measure" + }, + { + "startTime": 11285.93, + "name": "lh:audit:interactive", + "duration": 1.19, + "entryType": "measure" + }, + { + "startTime": 11286.31, + "name": "lh:computed:Interactive", + "duration": 0.1, + "entryType": "measure" + }, + { + "startTime": 11287.37, + "name": "lh:audit:user-timings", + "duration": 1.51, + "entryType": "measure" + }, + { + "startTime": 11287.64, + "name": "lh:computed:UserTimings", + "duration": 0.6, + "entryType": "measure" + }, + { + "startTime": 11289.09, + "name": "lh:audit:critical-request-chains", + "duration": 4.74, + "entryType": "measure" + }, + { + "startTime": 11289.45, + "name": "lh:computed:CriticalRequestChains", + "duration": 1.03, + "entryType": "measure" + }, + { + "startTime": 11294.3, + "name": "lh:audit:redirects", + "duration": 2.04, + "entryType": "measure" + }, + { + "startTime": 11296.82, + "name": "lh:audit:image-aspect-ratio", + "duration": 2.16, + "entryType": "measure" + }, + { + "startTime": 11299.33, + "name": "lh:audit:image-size-responsive", + "duration": 1.76, + "entryType": "measure" + }, + { + "startTime": 11299.76, + "name": "lh:computed:ImageRecords", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 11301.43, + "name": "lh:audit:deprecations", + "duration": 1.18, + "entryType": "measure" + }, + { + "startTime": 11302.87, + "name": "lh:audit:third-party-cookies", + "duration": 1.02, + "entryType": "measure" + }, + { + "startTime": 11304.22, + "name": "lh:audit:mainthread-work-breakdown", + "duration": 11.94, + "entryType": "measure" + }, + { + "startTime": 11304.69, + "name": "lh:computed:MainThreadTasks", + "duration": 8.84, + "entryType": "measure" + }, + { + "startTime": 11316.54, + "name": "lh:audit:bootup-time", + "duration": 6.5, + "entryType": "measure" + }, + { + "startTime": 11318.56, + "name": "lh:computed:TBTImpactTasks", + "duration": 2, + "entryType": "measure" + }, + { + "startTime": 11323.33, + "name": "lh:audit:uses-rel-preconnect", + "duration": 3.21, + "entryType": "measure" + }, + { + "startTime": 11326.98, + "name": "lh:audit:font-display", + "duration": 2.76, + "entryType": "measure" + }, + { + "startTime": 11329.76, + "name": "lh:audit:diagnostics", + "duration": 0.91, + "entryType": "measure" + }, + { + "startTime": 11330.68, + "name": "lh:audit:network-requests", + "duration": 6.19, + "entryType": "measure" + }, + { + "startTime": 11331.01, + "name": "lh:computed:EntityClassification", + "duration": 3.07, + "entryType": "measure" + }, + { + "startTime": 11337.26, + "name": "lh:audit:network-rtt", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 11338.83, + "name": "lh:audit:network-server-latency", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 11340.16, + "name": "lh:audit:main-thread-tasks", + "duration": 0.5, + "entryType": "measure" + }, + { + "startTime": 11340.69, + "name": "lh:audit:metrics", + "duration": 3.34, + "entryType": "measure" + }, + { + "startTime": 11340.93, + "name": "lh:computed:TimingSummary", + "duration": 2.81, + "entryType": "measure" + }, + { + "startTime": 11341.37, + "name": "lh:computed:FirstContentfulPaintAllFrames", + "duration": 0.09, + "entryType": "measure" + }, + { + "startTime": 11341.5, + "name": "lh:computed:LargestContentfulPaintAllFrames", + "duration": 0.06, + "entryType": "measure" + }, + { + "startTime": 11341.63, + "name": "lh:computed:LCPBreakdown", + "duration": 1.15, + "entryType": "measure" + }, + { + "startTime": 11341.74, + "name": "lh:computed:TimeToFirstByte", + "duration": 0.2, + "entryType": "measure" + }, + { + "startTime": 11341.96, + "name": "lh:computed:LCPImageRecord", + "duration": 0.8, + "entryType": "measure" + }, + { + "startTime": 11344.06, + "name": "lh:audit:resource-summary", + "duration": 1.38, + "entryType": "measure" + }, + { + "startTime": 11344.33, + "name": "lh:computed:ResourceSummary", + "duration": 0.44, + "entryType": "measure" + }, + { + "startTime": 11345.74, + "name": "lh:audit:third-party-summary", + "duration": 3.2, + "entryType": "measure" + }, + { + "startTime": 11349.34, + "name": "lh:audit:third-party-facades", + "duration": 2.48, + "entryType": "measure" + }, + { + "startTime": 11352.06, + "name": "lh:audit:largest-contentful-paint-element", + "duration": 1.63, + "entryType": "measure" + }, + { + "startTime": 11353.96, + "name": "lh:audit:lcp-lazy-loaded", + "duration": 1.09, + "entryType": "measure" + }, + { + "startTime": 11355.42, + "name": "lh:audit:layout-shifts", + "duration": 2.45, + "entryType": "measure" + }, + { + "startTime": 11358.22, + "name": "lh:audit:long-tasks", + "duration": 3.81, + "entryType": "measure" + }, + { + "startTime": 11362.29, + "name": "lh:audit:non-composited-animations", + "duration": 0.97, + "entryType": "measure" + }, + { + "startTime": 11363.52, + "name": "lh:audit:unsized-images", + "duration": 1.47, + "entryType": "measure" + }, + { + "startTime": 11365.61, + "name": "lh:audit:valid-source-maps", + "duration": 1.43, + "entryType": "measure" + }, + { + "startTime": 11367.39, + "name": "lh:audit:prioritize-lcp-image", + "duration": 0.96, + "entryType": "measure" + }, + { + "startTime": 11368.56, + "name": "lh:audit:csp-xss", + "duration": 1.14, + "entryType": "measure" + }, + { + "startTime": 11369.91, + "name": "lh:audit:has-hsts", + "duration": 1.09, + "entryType": "measure" + }, + { + "startTime": 11371.25, + "name": "lh:audit:origin-isolation", + "duration": 1.19, + "entryType": "measure" + }, + { + "startTime": 11372.65, + "name": "lh:audit:clickjacking-mitigation", + "duration": 1.29, + "entryType": "measure" + }, + { + "startTime": 11374.37, + "name": "lh:audit:trusted-types-xss", + "duration": 1.84, + "entryType": "measure" + }, + { + "startTime": 11376.24, + "name": "lh:audit:script-treemap-data", + "duration": 17, + "entryType": "measure" + }, + { + "startTime": 11376.71, + "name": "lh:computed:ModuleDuplication", + "duration": 0.31, + "entryType": "measure" + }, + { + "startTime": 11377.07, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 0.3, + "entryType": "measure" + }, + { + "startTime": 11377.45, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 15.5, + "entryType": "measure" + }, + { + "startTime": 11393.58, + "name": "lh:audit:accesskeys", + "duration": 1.15, + "entryType": "measure" + }, + { + "startTime": 11395.26, + "name": "lh:audit:aria-allowed-attr", + "duration": 7.87, + "entryType": "measure" + }, + { + "startTime": 11403.77, + "name": "lh:audit:aria-allowed-role", + "duration": 7.51, + "entryType": "measure" + }, + { + "startTime": 11411.89, + "name": "lh:audit:aria-command-name", + "duration": 2.28, + "entryType": "measure" + }, + { + "startTime": 11414.72, + "name": "lh:audit:aria-conditional-attr", + "duration": 5.04, + "entryType": "measure" + }, + { + "startTime": 11420.01, + "name": "lh:audit:aria-deprecated-role", + "duration": 3.3, + "entryType": "measure" + }, + { + "startTime": 11423.71, + "name": "lh:audit:aria-dialog-name", + "duration": 1.77, + "entryType": "measure" + }, + { + "startTime": 11425.95, + "name": "lh:audit:aria-hidden-body", + "duration": 3.06, + "entryType": "measure" + }, + { + "startTime": 11429.33, + "name": "lh:audit:aria-hidden-focus", + "duration": 4.56, + "entryType": "measure" + }, + { + "startTime": 11434.45, + "name": "lh:audit:aria-input-field-name", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 11436.18, + "name": "lh:audit:aria-meter-name", + "duration": 2.29, + "entryType": "measure" + }, + { + "startTime": 11438.91, + "name": "lh:audit:aria-progressbar-name", + "duration": 2.06, + "entryType": "measure" + }, + { + "startTime": 11441.69, + "name": "lh:audit:aria-prohibited-attr", + "duration": 3.81, + "entryType": "measure" + }, + { + "startTime": 11445.74, + "name": "lh:audit:aria-required-attr", + "duration": 3.32, + "entryType": "measure" + }, + { + "startTime": 11449.38, + "name": "lh:audit:aria-required-children", + "duration": 1.6, + "entryType": "measure" + }, + { + "startTime": 11451.35, + "name": "lh:audit:aria-required-parent", + "duration": 1.33, + "entryType": "measure" + }, + { + "startTime": 11452.9, + "name": "lh:audit:aria-roles", + "duration": 4.53, + "entryType": "measure" + }, + { + "startTime": 11458.02, + "name": "lh:audit:aria-text", + "duration": 2.45, + "entryType": "measure" + }, + { + "startTime": 11460.81, + "name": "lh:audit:aria-toggle-field-name", + "duration": 3.88, + "entryType": "measure" + }, + { + "startTime": 11465.05, + "name": "lh:audit:aria-tooltip-name", + "duration": 2.23, + "entryType": "measure" + }, + { + "startTime": 11467.62, + "name": "lh:audit:aria-treeitem-name", + "duration": 1.62, + "entryType": "measure" + }, + { + "startTime": 11469.45, + "name": "lh:audit:aria-valid-attr-value", + "duration": 4.48, + "entryType": "measure" + }, + { + "startTime": 11474.26, + "name": "lh:audit:aria-valid-attr", + "duration": 3.62, + "entryType": "measure" + }, + { + "startTime": 11478.12, + "name": "lh:audit:button-name", + "duration": 3.31, + "entryType": "measure" + }, + { + "startTime": 11481.75, + "name": "lh:audit:bypass", + "duration": 3.6, + "entryType": "measure" + }, + { + "startTime": 11485.61, + "name": "lh:audit:color-contrast", + "duration": 3.35, + "entryType": "measure" + }, + { + "startTime": 11489.28, + "name": "lh:audit:definition-list", + "duration": 2.27, + "entryType": "measure" + }, + { + "startTime": 11491.87, + "name": "lh:audit:dlitem", + "duration": 2.03, + "entryType": "measure" + }, + { + "startTime": 11494.22, + "name": "lh:audit:document-title", + "duration": 3.25, + "entryType": "measure" + }, + { + "startTime": 11497.7, + "name": "lh:audit:duplicate-id-aria", + "duration": 3.14, + "entryType": "measure" + }, + { + "startTime": 11501.09, + "name": "lh:audit:empty-heading", + "duration": 7.59, + "entryType": "measure" + }, + { + "startTime": 11508.98, + "name": "lh:audit:form-field-multiple-labels", + "duration": 3.67, + "entryType": "measure" + }, + { + "startTime": 11512.92, + "name": "lh:audit:frame-title", + "duration": 2.65, + "entryType": "measure" + }, + { + "startTime": 11516.14, + "name": "lh:audit:heading-order", + "duration": 3.49, + "entryType": "measure" + }, + { + "startTime": 11519.88, + "name": "lh:audit:html-has-lang", + "duration": 3.04, + "entryType": "measure" + }, + { + "startTime": 11523.25, + "name": "lh:audit:html-lang-valid", + "duration": 4.17, + "entryType": "measure" + }, + { + "startTime": 11527.81, + "name": "lh:audit:html-xml-lang-mismatch", + "duration": 2.5, + "entryType": "measure" + }, + { + "startTime": 11530.55, + "name": "lh:audit:identical-links-same-purpose", + "duration": 4.84, + "entryType": "measure" + }, + { + "startTime": 11535.63, + "name": "lh:audit:image-alt", + "duration": 2.83, + "entryType": "measure" + }, + { + "startTime": 11538.69, + "name": "lh:audit:image-redundant-alt", + "duration": 11.28, + "entryType": "measure" + }, + { + "startTime": 11550.28, + "name": "lh:audit:input-button-name", + "duration": 2.46, + "entryType": "measure" + }, + { + "startTime": 11553.19, + "name": "lh:audit:input-image-alt", + "duration": 2.18, + "entryType": "measure" + }, + { + "startTime": 11555.59, + "name": "lh:audit:label-content-name-mismatch", + "duration": 3.26, + "entryType": "measure" + }, + { + "startTime": 11559.08, + "name": "lh:audit:label", + "duration": 2.9, + "entryType": "measure" + }, + { + "startTime": 11562.27, + "name": "lh:audit:landmark-one-main", + "duration": 3, + "entryType": "measure" + }, + { + "startTime": 11565.7, + "name": "lh:audit:link-name", + "duration": 4.65, + "entryType": "measure" + }, + { + "startTime": 11570.7, + "name": "lh:audit:link-in-text-block", + "duration": 2.12, + "entryType": "measure" + }, + { + "startTime": 11573.04, + "name": "lh:audit:list", + "duration": 3.58, + "entryType": "measure" + }, + { + "startTime": 11576.86, + "name": "lh:audit:listitem", + "duration": 3.07, + "entryType": "measure" + }, + { + "startTime": 11580.27, + "name": "lh:audit:meta-refresh", + "duration": 8.97, + "entryType": "measure" + }, + { + "startTime": 11589.49, + "name": "lh:audit:meta-viewport", + "duration": 3.83, + "entryType": "measure" + }, + { + "startTime": 11593.56, + "name": "lh:audit:object-alt", + "duration": 2.38, + "entryType": "measure" + }, + { + "startTime": 11596.2, + "name": "lh:audit:select-name", + "duration": 3.27, + "entryType": "measure" + }, + { + "startTime": 11599.89, + "name": "lh:audit:skip-link", + "duration": 3.48, + "entryType": "measure" + }, + { + "startTime": 11603.71, + "name": "lh:audit:tabindex", + "duration": 2.3, + "entryType": "measure" + }, + { + "startTime": 11606.28, + "name": "lh:audit:table-duplicate-name", + "duration": 3.17, + "entryType": "measure" + }, + { + "startTime": 11609.81, + "name": "lh:audit:table-fake-caption", + "duration": 3.38, + "entryType": "measure" + }, + { + "startTime": 11613.75, + "name": "lh:audit:target-size", + "duration": 4.54, + "entryType": "measure" + }, + { + "startTime": 11618.6, + "name": "lh:audit:td-has-header", + "duration": 9.28, + "entryType": "measure" + }, + { + "startTime": 11628.24, + "name": "lh:audit:td-headers-attr", + "duration": 4.47, + "entryType": "measure" + }, + { + "startTime": 11633.04, + "name": "lh:audit:th-has-data-cells", + "duration": 3.16, + "entryType": "measure" + }, + { + "startTime": 11636.51, + "name": "lh:audit:valid-lang", + "duration": 2.68, + "entryType": "measure" + }, + { + "startTime": 11639.61, + "name": "lh:audit:video-caption", + "duration": 3.69, + "entryType": "measure" + }, + { + "startTime": 11643.35, + "name": "lh:audit:custom-controls-labels", + "duration": 0.14, + "entryType": "measure" + }, + { + "startTime": 11643.5, + "name": "lh:audit:custom-controls-roles", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 11643.52, + "name": "lh:audit:focus-traps", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.55, + "name": "lh:audit:focusable-controls", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.56, + "name": "lh:audit:interactive-element-affordance", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.58, + "name": "lh:audit:logical-tab-order", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.6, + "name": "lh:audit:managed-focus", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.61, + "name": "lh:audit:offscreen-content-hidden", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.63, + "name": "lh:audit:use-landmarks", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.65, + "name": "lh:audit:visual-order-follows-dom", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11643.9, + "name": "lh:audit:uses-long-cache-ttl", + "duration": 1.97, + "entryType": "measure" + }, + { + "startTime": 11646.09, + "name": "lh:audit:total-byte-weight", + "duration": 1.63, + "entryType": "measure" + }, + { + "startTime": 11648.01, + "name": "lh:audit:offscreen-images", + "duration": 4.01, + "entryType": "measure" + }, + { + "startTime": 11652.27, + "name": "lh:audit:render-blocking-resources", + "duration": 3.46, + "entryType": "measure" + }, + { + "startTime": 11652.76, + "name": "lh:computed:UnusedCSS", + "duration": 0.46, + "entryType": "measure" + }, + { + "startTime": 11653.25, + "name": "lh:computed:NavigationInsights", + "duration": 0.08, + "entryType": "measure" + }, + { + "startTime": 11653.39, + "name": "lh:computed:FirstContentfulPaint", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 11655.9, + "name": "lh:audit:unminified-css", + "duration": 9.23, + "entryType": "measure" + }, + { + "startTime": 11665.39, + "name": "lh:audit:unminified-javascript", + "duration": 44.57, + "entryType": "measure" + }, + { + "startTime": 11710.27, + "name": "lh:audit:unused-css-rules", + "duration": 2.49, + "entryType": "measure" + }, + { + "startTime": 11712.96, + "name": "lh:audit:unused-javascript", + "duration": 2.82, + "entryType": "measure" + }, + { + "startTime": 11715.97, + "name": "lh:audit:modern-image-formats", + "duration": 2.23, + "entryType": "measure" + }, + { + "startTime": 11718.38, + "name": "lh:audit:uses-optimized-images", + "duration": 5.81, + "entryType": "measure" + }, + { + "startTime": 11724.42, + "name": "lh:audit:uses-text-compression", + "duration": 3.03, + "entryType": "measure" + }, + { + "startTime": 11727.63, + "name": "lh:audit:uses-responsive-images", + "duration": 3.06, + "entryType": "measure" + }, + { + "startTime": 11730.91, + "name": "lh:audit:efficient-animated-content", + "duration": 3.02, + "entryType": "measure" + }, + { + "startTime": 11734.34, + "name": "lh:audit:duplicated-javascript", + "duration": 3.56, + "entryType": "measure" + }, + { + "startTime": 11738.28, + "name": "lh:audit:legacy-javascript", + "duration": 44.98, + "entryType": "measure" + }, + { + "startTime": 11783.64, + "name": "lh:audit:doctype", + "duration": 1.09, + "entryType": "measure" + }, + { + "startTime": 11784.98, + "name": "lh:audit:charset", + "duration": 1.18, + "entryType": "measure" + }, + { + "startTime": 11786.7, + "name": "lh:audit:dom-size", + "duration": 2.19, + "entryType": "measure" + }, + { + "startTime": 11789.24, + "name": "lh:audit:geolocation-on-start", + "duration": 1.35, + "entryType": "measure" + }, + { + "startTime": 11791.21, + "name": "lh:audit:inspector-issues", + "duration": 1.31, + "entryType": "measure" + }, + { + "startTime": 11792.77, + "name": "lh:audit:no-document-write", + "duration": 1.04, + "entryType": "measure" + }, + { + "startTime": 11794, + "name": "lh:audit:js-libraries", + "duration": 0.74, + "entryType": "measure" + }, + { + "startTime": 11794.98, + "name": "lh:audit:notification-on-start", + "duration": 0.93, + "entryType": "measure" + }, + { + "startTime": 11796.18, + "name": "lh:audit:paste-preventing-inputs", + "duration": 0.85, + "entryType": "measure" + }, + { + "startTime": 11797.26, + "name": "lh:audit:uses-http2", + "duration": 2.44, + "entryType": "measure" + }, + { + "startTime": 11799.95, + "name": "lh:audit:uses-passive-event-listeners", + "duration": 0.99, + "entryType": "measure" + }, + { + "startTime": 11801.24, + "name": "lh:audit:meta-description", + "duration": 0.79, + "entryType": "measure" + }, + { + "startTime": 11802.31, + "name": "lh:audit:http-status-code", + "duration": 0.88, + "entryType": "measure" + }, + { + "startTime": 11803.74, + "name": "lh:audit:font-size", + "duration": 1.13, + "entryType": "measure" + }, + { + "startTime": 11805.17, + "name": "lh:audit:link-text", + "duration": 1.42, + "entryType": "measure" + }, + { + "startTime": 11806.97, + "name": "lh:audit:crawlable-anchors", + "duration": 2.37, + "entryType": "measure" + }, + { + "startTime": 11809.91, + "name": "lh:audit:is-crawlable", + "duration": 2.28, + "entryType": "measure" + }, + { + "startTime": 11812.45, + "name": "lh:audit:robots-txt", + "duration": 1.84, + "entryType": "measure" + }, + { + "startTime": 11814.53, + "name": "lh:audit:hreflang", + "duration": 0.99, + "entryType": "measure" + }, + { + "startTime": 11815.76, + "name": "lh:audit:canonical", + "duration": 1.01, + "entryType": "measure" + }, + { + "startTime": 11816.96, + "name": "lh:audit:structured-data", + "duration": 0.56, + "entryType": "measure" + }, + { + "startTime": 11817.76, + "name": "lh:audit:bf-cache", + "duration": 0.92, + "entryType": "measure" + }, + { + "startTime": 11818.91, + "name": "lh:audit:cache-insight", + "duration": 1.29, + "entryType": "measure" + }, + { + "startTime": 11820.88, + "name": "lh:audit:cls-culprits-insight", + "duration": 2.29, + "entryType": "measure" + }, + { + "startTime": 11823.54, + "name": "lh:audit:document-latency-insight", + "duration": 5.77, + "entryType": "measure" + }, + { + "startTime": 11829.57, + "name": "lh:audit:dom-size-insight", + "duration": 1.24, + "entryType": "measure" + }, + { + "startTime": 11831.04, + "name": "lh:audit:duplicated-javascript-insight", + "duration": 0.95, + "entryType": "measure" + }, + { + "startTime": 11832.27, + "name": "lh:audit:font-display-insight", + "duration": 0.96, + "entryType": "measure" + }, + { + "startTime": 11833.47, + "name": "lh:audit:forced-reflow-insight", + "duration": 1.1, + "entryType": "measure" + }, + { + "startTime": 11834.8, + "name": "lh:audit:image-delivery-insight", + "duration": 1.02, + "entryType": "measure" + }, + { + "startTime": 11836.08, + "name": "lh:audit:inp-breakdown-insight", + "duration": 1.25, + "entryType": "measure" + }, + { + "startTime": 11837.56, + "name": "lh:audit:lcp-breakdown-insight", + "duration": 1.36, + "entryType": "measure" + }, + { + "startTime": 11839.24, + "name": "lh:audit:lcp-discovery-insight", + "duration": 1.06, + "entryType": "measure" + }, + { + "startTime": 11840.67, + "name": "lh:audit:legacy-javascript-insight", + "duration": 1.78, + "entryType": "measure" + }, + { + "startTime": 11842.7, + "name": "lh:audit:modern-http-insight", + "duration": 0.99, + "entryType": "measure" + }, + { + "startTime": 11843.92, + "name": "lh:audit:network-dependency-tree-insight", + "duration": 1.46, + "entryType": "measure" + }, + { + "startTime": 11845.61, + "name": "lh:audit:render-blocking-insight", + "duration": 1.27, + "entryType": "measure" + }, + { + "startTime": 11847.12, + "name": "lh:audit:third-parties-insight", + "duration": 10.77, + "entryType": "measure" + }, + { + "startTime": 11858.17, + "name": "lh:audit:viewport-insight", + "duration": 1.36, + "entryType": "measure" + }, + { + "startTime": 11859.58, + "name": "lh:runner:generate", + "duration": 0.61, + "entryType": "measure" + } + ], + "total": 8899.27 + }, + "i18n": { + "rendererFormattedStrings": { + "calculatorLink": "See calculator.", + "collapseView": "Collapse view", + "crcInitialNavigation": "Initial Navigation", + "crcLongestDurationLabel": "Maximum critical path latency:", + "dropdownCopyJSON": "Copy JSON", + "dropdownDarkTheme": "Toggle Dark Theme", + "dropdownPrintExpanded": "Print Expanded", + "dropdownPrintSummary": "Print Summary", + "dropdownSaveGist": "Save as Gist", + "dropdownSaveHTML": "Save as HTML", + "dropdownSaveJSON": "Save as JSON", + "dropdownViewUnthrottledTrace": "View Unthrottled Trace", + "dropdownViewer": "Open in Viewer", + "errorLabel": "Error!", + "errorMissingAuditInfo": "Report error: no audit information", + "expandView": "Expand view", + "firstPartyChipLabel": "1st party", + "footerIssue": "File an issue", + "goBackToAudits": "Go back to audits", + "hide": "Hide", + "insightsNotice": "Later this year, insights will replace performance audits. [Learn more and provide feedback here](https://github.com/GoogleChrome/lighthouse/discussions/16462).", + "labDataTitle": "Lab Data", + "lsPerformanceCategoryDescription": "[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.", + "manualAuditsGroupTitle": "Additional items to manually check", + "notApplicableAuditsGroupTitle": "Not applicable", + "openInANewTabTooltip": "Open in a new tab", + "opportunityResourceColumnLabel": "Opportunity", + "opportunitySavingsColumnLabel": "Estimated Savings", + "passedAuditsGroupTitle": "Passed audits", + "runtimeAnalysisWindow": "Initial page load", + "runtimeAnalysisWindowSnapshot": "Point-in-time snapshot", + "runtimeAnalysisWindowTimespan": "User interactions timespan", + "runtimeCustom": "Custom throttling", + "runtimeDesktopEmulation": "Emulated Desktop", + "runtimeMobileEmulation": "Emulated Moto G Power", + "runtimeNoEmulation": "No emulation", + "runtimeSettingsAxeVersion": "Axe version", + "runtimeSettingsBenchmark": "Unthrottled CPU/Memory Power", + "runtimeSettingsCPUThrottling": "CPU throttling", + "runtimeSettingsDevice": "Device", + "runtimeSettingsNetworkThrottling": "Network throttling", + "runtimeSettingsScreenEmulation": "Screen emulation", + "runtimeSettingsUANetwork": "User agent (network)", + "runtimeSingleLoad": "Single page session", + "runtimeSingleLoadTooltip": "This data is taken from a single page session, as opposed to field data summarizing many sessions.", + "runtimeSlow4g": "Slow 4G throttling", + "runtimeUnknown": "Unknown", + "show": "Show", + "showRelevantAudits": "Show audits relevant to:", + "snippetCollapseButtonLabel": "Collapse snippet", + "snippetExpandButtonLabel": "Expand snippet", + "thirdPartyResourcesLabel": "Show 3rd-party resources", + "throttlingProvided": "Provided by environment", + "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", + "tryInsights": "Try insights", + "unattributable": "Unattributable", + "varianceDisclaimer": "Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.", + "viewTraceLabel": "View Trace", + "viewTreemapLabel": "View Treemap", + "warningAuditsGroupTitle": "Passed audits but with warnings", + "warningHeader": "Warnings: " + }, + "icuMessagePaths": { + "core/audits/is-on-https.js | title": ["audits[is-on-https].title"], + "core/audits/is-on-https.js | description": [ + "audits[is-on-https].description" + ], + "core/audits/is-on-https.js | columnInsecureURL": [ + "audits[is-on-https].details.headings[0].label" + ], + "core/audits/is-on-https.js | columnResolution": [ + "audits[is-on-https].details.headings[1].label" + ], + "core/audits/redirects-http.js | title": ["audits[redirects-http].title"], + "core/audits/redirects-http.js | description": [ + "audits[redirects-http].description" + ], + "core/audits/viewport.js | title": ["audits.viewport.title"], + "core/audits/viewport.js | description": ["audits.viewport.description"], + "core/lib/i18n/i18n.js | firstContentfulPaintMetric": [ + "audits[first-contentful-paint].title" + ], + "core/audits/metrics/first-contentful-paint.js | description": [ + "audits[first-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | seconds": [ + { + "values": { + "timeInMs": 648.1821 + }, + "path": "audits[first-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 748.77315 + }, + "path": "audits[largest-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 648.1821 + }, + "path": "audits[speed-index].displayValue" + }, + { + "values": { + "timeInMs": 748.77315 + }, + "path": "audits.interactive.displayValue" + }, + { + "values": { + "timeInMs": 587.3279999999983 + }, + "path": "audits[mainthread-work-breakdown].displayValue" + }, + { + "values": { + "timeInMs": 103.62200000000016 + }, + "path": "audits[bootup-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | largestContentfulPaintMetric": [ + "audits[largest-contentful-paint].title" + ], + "core/audits/metrics/largest-contentful-paint.js | description": [ + "audits[largest-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | firstMeaningfulPaintMetric": [ + "audits[first-meaningful-paint].title" + ], + "core/audits/metrics/first-meaningful-paint.js | description": [ + "audits[first-meaningful-paint].description" + ], + "core/lib/i18n/i18n.js | speedIndexMetric": ["audits[speed-index].title"], + "core/audits/metrics/speed-index.js | description": [ + "audits[speed-index].description" + ], + "core/lib/i18n/i18n.js | totalBlockingTimeMetric": [ + "audits[total-blocking-time].title" + ], + "core/audits/metrics/total-blocking-time.js | description": [ + "audits[total-blocking-time].description" + ], + "core/lib/i18n/i18n.js | ms": [ + { + "values": { + "timeInMs": 0 + }, + "path": "audits[total-blocking-time].displayValue" + }, + { + "values": { + "timeInMs": 20 + }, + "path": "audits[max-potential-fid].displayValue" + }, + { + "values": { + "timeInMs": 0.03795 + }, + "path": "audits[network-rtt].displayValue" + }, + { + "values": { + "timeInMs": 20.591050000000003 + }, + "path": "audits[network-server-latency].displayValue" + }, + { + "values": { + "timeInMs": 748.77315 + }, + "path": "audits[largest-contentful-paint-element].displayValue" + } + ], + "core/lib/i18n/i18n.js | maxPotentialFIDMetric": [ + "audits[max-potential-fid].title" + ], + "core/audits/metrics/max-potential-fid.js | description": [ + "audits[max-potential-fid].description" + ], + "core/lib/i18n/i18n.js | cumulativeLayoutShiftMetric": [ + "audits[cumulative-layout-shift].title" + ], + "core/audits/metrics/cumulative-layout-shift.js | description": [ + "audits[cumulative-layout-shift].description" + ], + "core/audits/errors-in-console.js | failureTitle": [ + "audits[errors-in-console].title" + ], + "core/audits/errors-in-console.js | description": [ + "audits[errors-in-console].description" + ], + "core/lib/i18n/i18n.js | columnSource": [ + "audits[errors-in-console].details.headings[0].label", + "audits.deprecations.details.headings[1].label", + "audits[geolocation-on-start].details.headings[0].label", + "audits[no-document-write].details.headings[0].label", + "audits[notification-on-start].details.headings[0].label", + "audits[uses-passive-event-listeners].details.headings[0].label", + "audits[forced-reflow-insight].details.items[0].headings[0].label" + ], + "core/lib/i18n/i18n.js | columnDescription": [ + "audits[errors-in-console].details.headings[1].label", + "audits[csp-xss].details.headings[0].label", + "audits[has-hsts].details.headings[0].label", + "audits[origin-isolation].details.headings[0].label", + "audits[clickjacking-mitigation].details.headings[0].label", + "audits[trusted-types-xss].details.headings[0].label" + ], + "core/audits/server-response-time.js | title": [ + "audits[server-response-time].title" + ], + "core/audits/server-response-time.js | description": [ + "audits[server-response-time].description" + ], + "core/audits/server-response-time.js | displayValue": [ + { + "values": { + "timeInMs": 14.853 + }, + "path": "audits[server-response-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnURL": [ + "audits[server-response-time].details.headings[0].label", + "audits[image-aspect-ratio].details.headings[1].label", + "audits[image-size-responsive].details.headings[1].label", + "audits[third-party-cookies].details.headings[1].label", + "audits[bootup-time].details.headings[0].label", + "audits[font-display].details.headings[0].label", + "audits[network-rtt].details.headings[0].label", + "audits[network-server-latency].details.headings[0].label", + "audits[long-tasks].details.headings[0].label", + "audits[unsized-images].details.headings[1].label", + "audits[valid-source-maps].details.headings[0].label", + "audits[uses-long-cache-ttl].details.headings[0].label", + "audits[total-byte-weight].details.headings[0].label", + "audits[render-blocking-resources].details.headings[0].label", + "audits[unused-javascript].details.headings[0].label", + "audits[font-display-insight].details.headings[0].label", + "audits[image-delivery-insight].details.headings[0].label", + "audits[legacy-javascript-insight].details.headings[0].label", + "audits[modern-http-insight].details.headings[0].label", + "audits[render-blocking-insight].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnTimeSpent": [ + "audits[server-response-time].details.headings[1].label", + "audits[mainthread-work-breakdown].details.headings[1].label", + "audits[network-rtt].details.headings[1].label", + "audits[network-server-latency].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | interactiveMetric": ["audits.interactive.title"], + "core/audits/metrics/interactive.js | description": [ + "audits.interactive.description" + ], + "core/audits/user-timings.js | title": ["audits[user-timings].title"], + "core/audits/user-timings.js | description": [ + "audits[user-timings].description" + ], + "core/lib/i18n/i18n.js | columnName": [ + "audits[user-timings].details.headings[0].label", + "audits[third-party-cookies].details.headings[0].label" + ], + "core/audits/user-timings.js | columnType": [ + "audits[user-timings].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnStartTime": [ + "audits[user-timings].details.headings[2].label", + "audits[long-tasks].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnDuration": [ + "audits[user-timings].details.headings[3].label", + "audits[long-tasks].details.headings[2].label", + "audits[render-blocking-resources].details.headings[2].label", + "audits[lcp-breakdown-insight].details.items[0].headings[1].label", + "audits[render-blocking-insight].details.headings[2].label" + ], + "core/audits/critical-request-chains.js | title": [ + "audits[critical-request-chains].title" + ], + "core/audits/critical-request-chains.js | description": [ + "audits[critical-request-chains].description" + ], + "core/audits/critical-request-chains.js | displayValue": [ + { + "values": { + "itemCount": 6 + }, + "path": "audits[critical-request-chains].displayValue" + } + ], + "core/audits/redirects.js | title": ["audits.redirects.title"], + "core/audits/redirects.js | description": [ + "audits.redirects.description" + ], + "core/audits/image-aspect-ratio.js | title": [ + "audits[image-aspect-ratio].title" + ], + "core/audits/image-aspect-ratio.js | description": [ + "audits[image-aspect-ratio].description" + ], + "core/audits/image-aspect-ratio.js | columnDisplayed": [ + "audits[image-aspect-ratio].details.headings[2].label" + ], + "core/audits/image-aspect-ratio.js | columnActual": [ + "audits[image-aspect-ratio].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | title": [ + "audits[image-size-responsive].title" + ], + "core/audits/image-size-responsive.js | description": [ + "audits[image-size-responsive].description" + ], + "core/audits/image-size-responsive.js | columnDisplayed": [ + "audits[image-size-responsive].details.headings[2].label" + ], + "core/audits/image-size-responsive.js | columnActual": [ + "audits[image-size-responsive].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | columnExpected": [ + "audits[image-size-responsive].details.headings[4].label" + ], + "core/audits/deprecations.js | title": ["audits.deprecations.title"], + "core/audits/deprecations.js | description": [ + "audits.deprecations.description" + ], + "core/audits/deprecations.js | columnDeprecate": [ + "audits.deprecations.details.headings[0].label" + ], + "core/audits/third-party-cookies.js | title": [ + "audits[third-party-cookies].title" + ], + "core/audits/third-party-cookies.js | description": [ + "audits[third-party-cookies].description" + ], + "core/audits/mainthread-work-breakdown.js | title": [ + "audits[mainthread-work-breakdown].title" + ], + "core/audits/mainthread-work-breakdown.js | description": [ + "audits[mainthread-work-breakdown].description" + ], + "core/audits/mainthread-work-breakdown.js | columnCategory": [ + "audits[mainthread-work-breakdown].details.headings[0].label" + ], + "core/audits/bootup-time.js | title": ["audits[bootup-time].title"], + "core/audits/bootup-time.js | description": [ + "audits[bootup-time].description" + ], + "core/audits/bootup-time.js | columnTotal": [ + "audits[bootup-time].details.headings[1].label" + ], + "core/audits/bootup-time.js | columnScriptEval": [ + "audits[bootup-time].details.headings[2].label" + ], + "core/audits/bootup-time.js | columnScriptParse": [ + "audits[bootup-time].details.headings[3].label" + ], + "core/audits/uses-rel-preconnect.js | title": [ + "audits[uses-rel-preconnect].title" + ], + "core/audits/uses-rel-preconnect.js | description": [ + "audits[uses-rel-preconnect].description" + ], + "core/audits/font-display.js | title": ["audits[font-display].title"], + "core/audits/font-display.js | description": [ + "audits[font-display].description" + ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[font-display].details.headings[1].label", + "audits[unused-javascript].details.headings[2].label", + "audits[font-display-insight].details.headings[1].label", + "audits[image-delivery-insight].details.headings[2].label" + ], + "core/audits/network-rtt.js | title": ["audits[network-rtt].title"], + "core/audits/network-rtt.js | description": [ + "audits[network-rtt].description" + ], + "core/audits/network-server-latency.js | title": [ + "audits[network-server-latency].title" + ], + "core/audits/network-server-latency.js | description": [ + "audits[network-server-latency].description" + ], + "core/lib/i18n/i18n.js | columnResourceType": [ + "audits[resource-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnRequests": [ + "audits[resource-summary].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnTransferSize": [ + "audits[resource-summary].details.headings[2].label", + "audits[third-party-summary].details.headings[1].label", + "audits[uses-long-cache-ttl].details.headings[2].label", + "audits[total-byte-weight].details.headings[1].label", + "audits[render-blocking-resources].details.headings[1].label", + "audits[unused-javascript].details.headings[1].label", + "audits[cache-insight].details.headings[2].label", + "audits[render-blocking-insight].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | total": [ + "audits[resource-summary].details.items[0].label", + "audits[cls-culprits-insight].details.items[0].items[0].node.value" + ], + "core/lib/i18n/i18n.js | fontResourceType": [ + "audits[resource-summary].details.items[1].label" + ], + "core/lib/i18n/i18n.js | scriptResourceType": [ + "audits[resource-summary].details.items[2].label" + ], + "core/lib/i18n/i18n.js | imageResourceType": [ + "audits[resource-summary].details.items[3].label" + ], + "core/lib/i18n/i18n.js | stylesheetResourceType": [ + "audits[resource-summary].details.items[4].label" + ], + "core/lib/i18n/i18n.js | otherResourceType": [ + "audits[resource-summary].details.items[5].label" + ], + "core/lib/i18n/i18n.js | documentResourceType": [ + "audits[resource-summary].details.items[6].label" + ], + "core/lib/i18n/i18n.js | mediaResourceType": [ + "audits[resource-summary].details.items[7].label" + ], + "core/lib/i18n/i18n.js | thirdPartyResourceType": [ + "audits[resource-summary].details.items[8].label" + ], + "core/audits/third-party-summary.js | title": [ + "audits[third-party-summary].title" + ], + "core/audits/third-party-summary.js | description": [ + "audits[third-party-summary].description" + ], + "core/audits/third-party-summary.js | displayValue": [ + { + "values": { + "timeInMs": 0 + }, + "path": "audits[third-party-summary].displayValue" + } + ], + "core/audits/third-party-summary.js | columnThirdParty": [ + "audits[third-party-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnBlockingTime": [ + "audits[third-party-summary].details.headings[2].label" + ], + "core/audits/third-party-facades.js | title": [ + "audits[third-party-facades].title" + ], + "core/audits/third-party-facades.js | description": [ + "audits[third-party-facades].description" + ], + "core/audits/largest-contentful-paint-element.js | title": [ + "audits[largest-contentful-paint-element].title" + ], + "core/audits/largest-contentful-paint-element.js | description": [ + "audits[largest-contentful-paint-element].description" + ], + "core/lib/i18n/i18n.js | columnElement": [ + "audits[largest-contentful-paint-element].details.items[0].headings[0].label", + "audits[layout-shifts].details.headings[0].label", + "audits[non-composited-animations].details.headings[0].label", + "audits[dom-size].details.headings[1].label", + "audits[cls-culprits-insight].details.items[0].headings[0].label", + "audits[dom-size-insight].details.headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPhase": [ + "audits[largest-contentful-paint-element].details.items[1].headings[0].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPercentOfLCP": [ + "audits[largest-contentful-paint-element].details.items[1].headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnTiming": [ + "audits[largest-contentful-paint-element].details.items[1].headings[2].label" + ], + "core/audits/largest-contentful-paint-element.js | itemTTFB": [ + "audits[largest-contentful-paint-element].details.items[1].items[0].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[1].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadTime": [ + "audits[largest-contentful-paint-element].details.items[1].items[2].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemRenderDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[3].phase" + ], + "core/audits/lcp-lazy-loaded.js | title": [ + "audits[lcp-lazy-loaded].title" + ], + "core/audits/lcp-lazy-loaded.js | description": [ + "audits[lcp-lazy-loaded].description" + ], + "core/audits/layout-shifts.js | title": ["audits[layout-shifts].title"], + "core/audits/layout-shifts.js | description": [ + "audits[layout-shifts].description" + ], + "core/audits/layout-shifts.js | displayValueShiftsFound": [ + { + "values": { + "shiftCount": 2 + }, + "path": "audits[layout-shifts].displayValue" + } + ], + "core/audits/layout-shifts.js | columnScore": [ + "audits[layout-shifts].details.headings[1].label" + ], + "core/audits/layout-shifts.js | rootCauseUnsizedMedia": [ + "audits[layout-shifts].details.items[1].subItems.items[0].cause" + ], + "core/audits/long-tasks.js | title": ["audits[long-tasks].title"], + "core/audits/long-tasks.js | description": [ + "audits[long-tasks].description" + ], + "core/audits/long-tasks.js | displayValue": [ + { + "values": { + "itemCount": 1 + }, + "path": "audits[long-tasks].displayValue" + } + ], + "core/audits/non-composited-animations.js | title": [ + "audits[non-composited-animations].title" + ], + "core/audits/non-composited-animations.js | description": [ + "audits[non-composited-animations].description" + ], + "core/audits/unsized-images.js | failureTitle": [ + "audits[unsized-images].title" + ], + "core/audits/unsized-images.js | description": [ + "audits[unsized-images].description" + ], + "core/audits/valid-source-maps.js | title": [ + "audits[valid-source-maps].title" + ], + "core/audits/valid-source-maps.js | description": [ + "audits[valid-source-maps].description" + ], + "core/audits/valid-source-maps.js | columnMapURL": [ + "audits[valid-source-maps].details.headings[1].label" + ], + "core/audits/prioritize-lcp-image.js | title": [ + "audits[prioritize-lcp-image].title" + ], + "core/audits/prioritize-lcp-image.js | description": [ + "audits[prioritize-lcp-image].description" + ], + "core/audits/csp-xss.js | title": ["audits[csp-xss].title"], + "core/audits/csp-xss.js | description": ["audits[csp-xss].description"], + "core/audits/csp-xss.js | columnDirective": [ + "audits[csp-xss].details.headings[1].label" + ], + "core/audits/csp-xss.js | columnSeverity": [ + "audits[csp-xss].details.headings[2].label" + ], + "core/lib/i18n/i18n.js | itemSeverityHigh": [ + "audits[csp-xss].details.items[0].severity", + "audits[has-hsts].details.items[0].severity", + "audits[origin-isolation].details.items[0].severity", + "audits[clickjacking-mitigation].details.items[0].severity", + "audits[trusted-types-xss].details.items[0].severity" + ], + "core/audits/csp-xss.js | noCsp": [ + "audits[csp-xss].details.items[0].description" + ], + "core/audits/has-hsts.js | title": ["audits[has-hsts].title"], + "core/audits/has-hsts.js | description": ["audits[has-hsts].description"], + "core/audits/has-hsts.js | columnDirective": [ + "audits[has-hsts].details.headings[1].label" + ], + "core/audits/has-hsts.js | columnSeverity": [ + "audits[has-hsts].details.headings[2].label" + ], + "core/audits/has-hsts.js | noHsts": [ + "audits[has-hsts].details.items[0].description" + ], + "core/audits/origin-isolation.js | title": [ + "audits[origin-isolation].title" + ], + "core/audits/origin-isolation.js | description": [ + "audits[origin-isolation].description" + ], + "core/audits/origin-isolation.js | columnDirective": [ + "audits[origin-isolation].details.headings[1].label" + ], + "core/audits/origin-isolation.js | columnSeverity": [ + "audits[origin-isolation].details.headings[2].label" + ], + "core/audits/origin-isolation.js | noCoop": [ + "audits[origin-isolation].details.items[0].description" + ], + "core/audits/clickjacking-mitigation.js | title": [ + "audits[clickjacking-mitigation].title" + ], + "core/audits/clickjacking-mitigation.js | description": [ + "audits[clickjacking-mitigation].description" + ], + "core/audits/clickjacking-mitigation.js | columnSeverity": [ + "audits[clickjacking-mitigation].details.headings[1].label" + ], + "core/audits/clickjacking-mitigation.js | noClickjackingMitigation": [ + "audits[clickjacking-mitigation].details.items[0].description" + ], + "core/audits/trusted-types-xss.js | title": [ + "audits[trusted-types-xss].title" + ], + "core/audits/trusted-types-xss.js | description": [ + "audits[trusted-types-xss].description" + ], + "core/audits/trusted-types-xss.js | columnSeverity": [ + "audits[trusted-types-xss].details.headings[1].label" + ], + "core/audits/trusted-types-xss.js | noTrustedTypesToMitigateXss": [ + "audits[trusted-types-xss].details.items[0].description" + ], + "core/audits/accessibility/accesskeys.js | title": [ + "audits.accesskeys.title" + ], + "core/audits/accessibility/accesskeys.js | description": [ + "audits.accesskeys.description" + ], + "core/audits/accessibility/aria-allowed-attr.js | title": [ + "audits[aria-allowed-attr].title" + ], + "core/audits/accessibility/aria-allowed-attr.js | description": [ + "audits[aria-allowed-attr].description" + ], + "core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[aria-allowed-attr].details.headings[0].label", + "audits[aria-allowed-role].details.headings[0].label", + "audits[aria-conditional-attr].details.headings[0].label", + "audits[aria-deprecated-role].details.headings[0].label", + "audits[aria-hidden-body].details.headings[0].label", + "audits[aria-hidden-focus].details.headings[0].label", + "audits[aria-prohibited-attr].details.headings[0].label", + "audits[aria-required-attr].details.headings[0].label", + "audits[aria-roles].details.headings[0].label", + "audits[aria-valid-attr-value].details.headings[0].label", + "audits[aria-valid-attr].details.headings[0].label", + "audits[button-name].details.headings[0].label", + "audits[color-contrast].details.headings[0].label", + "audits[document-title].details.headings[0].label", + "audits[heading-order].details.headings[0].label", + "audits[html-has-lang].details.headings[0].label", + "audits[html-lang-valid].details.headings[0].label", + "audits[image-alt].details.headings[0].label", + "audits[image-redundant-alt].details.headings[0].label", + "audits[label-content-name-mismatch].details.headings[0].label", + "audits.label.details.headings[0].label", + "audits[link-name].details.headings[0].label", + "audits.list.details.headings[0].label", + "audits.listitem.details.headings[0].label", + "audits[meta-viewport].details.headings[0].label", + "audits[skip-link].details.headings[0].label", + "audits[target-size].details.headings[0].label", + "audits[paste-preventing-inputs].details.headings[0].label" + ], + "core/audits/accessibility/aria-allowed-role.js | title": [ + "audits[aria-allowed-role].title" + ], + "core/audits/accessibility/aria-allowed-role.js | description": [ + "audits[aria-allowed-role].description" + ], + "core/audits/accessibility/aria-command-name.js | title": [ + "audits[aria-command-name].title" + ], + "core/audits/accessibility/aria-command-name.js | description": [ + "audits[aria-command-name].description" + ], + "core/audits/accessibility/aria-conditional-attr.js | title": [ + "audits[aria-conditional-attr].title" + ], + "core/audits/accessibility/aria-conditional-attr.js | description": [ + "audits[aria-conditional-attr].description" + ], + "core/audits/accessibility/aria-deprecated-role.js | title": [ + "audits[aria-deprecated-role].title" + ], + "core/audits/accessibility/aria-deprecated-role.js | description": [ + "audits[aria-deprecated-role].description" + ], + "core/audits/accessibility/aria-dialog-name.js | title": [ + "audits[aria-dialog-name].title" + ], + "core/audits/accessibility/aria-dialog-name.js | description": [ + "audits[aria-dialog-name].description" + ], + "core/audits/accessibility/aria-hidden-body.js | title": [ + "audits[aria-hidden-body].title" + ], + "core/audits/accessibility/aria-hidden-body.js | description": [ + "audits[aria-hidden-body].description" + ], + "core/audits/accessibility/aria-hidden-focus.js | title": [ + "audits[aria-hidden-focus].title" + ], + "core/audits/accessibility/aria-hidden-focus.js | description": [ + "audits[aria-hidden-focus].description" + ], + "core/audits/accessibility/aria-input-field-name.js | title": [ + "audits[aria-input-field-name].title" + ], + "core/audits/accessibility/aria-input-field-name.js | description": [ + "audits[aria-input-field-name].description" + ], + "core/audits/accessibility/aria-meter-name.js | title": [ + "audits[aria-meter-name].title" + ], + "core/audits/accessibility/aria-meter-name.js | description": [ + "audits[aria-meter-name].description" + ], + "core/audits/accessibility/aria-progressbar-name.js | title": [ + "audits[aria-progressbar-name].title" + ], + "core/audits/accessibility/aria-progressbar-name.js | description": [ + "audits[aria-progressbar-name].description" + ], + "core/audits/accessibility/aria-prohibited-attr.js | title": [ + "audits[aria-prohibited-attr].title" + ], + "core/audits/accessibility/aria-prohibited-attr.js | description": [ + "audits[aria-prohibited-attr].description" + ], + "core/audits/accessibility/aria-required-attr.js | title": [ + "audits[aria-required-attr].title" + ], + "core/audits/accessibility/aria-required-attr.js | description": [ + "audits[aria-required-attr].description" + ], + "core/audits/accessibility/aria-required-children.js | title": [ + "audits[aria-required-children].title" + ], + "core/audits/accessibility/aria-required-children.js | description": [ + "audits[aria-required-children].description" + ], + "core/audits/accessibility/aria-required-parent.js | title": [ + "audits[aria-required-parent].title" + ], + "core/audits/accessibility/aria-required-parent.js | description": [ + "audits[aria-required-parent].description" + ], + "core/audits/accessibility/aria-roles.js | title": [ + "audits[aria-roles].title" + ], + "core/audits/accessibility/aria-roles.js | description": [ + "audits[aria-roles].description" + ], + "core/audits/accessibility/aria-text.js | title": [ + "audits[aria-text].title" + ], + "core/audits/accessibility/aria-text.js | description": [ + "audits[aria-text].description" + ], + "core/audits/accessibility/aria-toggle-field-name.js | title": [ + "audits[aria-toggle-field-name].title" + ], + "core/audits/accessibility/aria-toggle-field-name.js | description": [ + "audits[aria-toggle-field-name].description" + ], + "core/audits/accessibility/aria-tooltip-name.js | title": [ + "audits[aria-tooltip-name].title" + ], + "core/audits/accessibility/aria-tooltip-name.js | description": [ + "audits[aria-tooltip-name].description" + ], + "core/audits/accessibility/aria-treeitem-name.js | title": [ + "audits[aria-treeitem-name].title" + ], + "core/audits/accessibility/aria-treeitem-name.js | description": [ + "audits[aria-treeitem-name].description" + ], + "core/audits/accessibility/aria-valid-attr-value.js | title": [ + "audits[aria-valid-attr-value].title" + ], + "core/audits/accessibility/aria-valid-attr-value.js | description": [ + "audits[aria-valid-attr-value].description" + ], + "core/audits/accessibility/aria-valid-attr.js | title": [ + "audits[aria-valid-attr].title" + ], + "core/audits/accessibility/aria-valid-attr.js | description": [ + "audits[aria-valid-attr].description" + ], + "core/audits/accessibility/button-name.js | title": [ + "audits[button-name].title" + ], + "core/audits/accessibility/button-name.js | description": [ + "audits[button-name].description" + ], + "core/audits/accessibility/bypass.js | title": ["audits.bypass.title"], + "core/audits/accessibility/bypass.js | description": [ + "audits.bypass.description" + ], + "core/audits/accessibility/color-contrast.js | title": [ + "audits[color-contrast].title" + ], + "core/audits/accessibility/color-contrast.js | description": [ + "audits[color-contrast].description" + ], + "core/audits/accessibility/definition-list.js | title": [ + "audits[definition-list].title" + ], + "core/audits/accessibility/definition-list.js | description": [ + "audits[definition-list].description" + ], + "core/audits/accessibility/dlitem.js | title": ["audits.dlitem.title"], + "core/audits/accessibility/dlitem.js | description": [ + "audits.dlitem.description" + ], + "core/audits/accessibility/document-title.js | title": [ + "audits[document-title].title" + ], + "core/audits/accessibility/document-title.js | description": [ + "audits[document-title].description" + ], + "core/audits/accessibility/duplicate-id-aria.js | title": [ + "audits[duplicate-id-aria].title" + ], + "core/audits/accessibility/duplicate-id-aria.js | description": [ + "audits[duplicate-id-aria].description" + ], + "core/audits/accessibility/empty-heading.js | title": [ + "audits[empty-heading].title" + ], + "core/audits/accessibility/empty-heading.js | description": [ + "audits[empty-heading].description" + ], + "core/audits/accessibility/form-field-multiple-labels.js | title": [ + "audits[form-field-multiple-labels].title" + ], + "core/audits/accessibility/form-field-multiple-labels.js | description": [ + "audits[form-field-multiple-labels].description" + ], + "core/audits/accessibility/frame-title.js | title": [ + "audits[frame-title].title" + ], + "core/audits/accessibility/frame-title.js | description": [ + "audits[frame-title].description" + ], + "core/audits/accessibility/heading-order.js | title": [ + "audits[heading-order].title" + ], + "core/audits/accessibility/heading-order.js | description": [ + "audits[heading-order].description" + ], + "core/audits/accessibility/html-has-lang.js | title": [ + "audits[html-has-lang].title" + ], + "core/audits/accessibility/html-has-lang.js | description": [ + "audits[html-has-lang].description" + ], + "core/audits/accessibility/html-lang-valid.js | title": [ + "audits[html-lang-valid].title" + ], + "core/audits/accessibility/html-lang-valid.js | description": [ + "audits[html-lang-valid].description" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | title": [ + "audits[html-xml-lang-mismatch].title" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | description": [ + "audits[html-xml-lang-mismatch].description" + ], + "core/audits/accessibility/identical-links-same-purpose.js | title": [ + "audits[identical-links-same-purpose].title" + ], + "core/audits/accessibility/identical-links-same-purpose.js | description": [ + "audits[identical-links-same-purpose].description" + ], + "core/audits/accessibility/image-alt.js | title": [ + "audits[image-alt].title" + ], + "core/audits/accessibility/image-alt.js | description": [ + "audits[image-alt].description" + ], + "core/audits/accessibility/image-redundant-alt.js | title": [ + "audits[image-redundant-alt].title" + ], + "core/audits/accessibility/image-redundant-alt.js | description": [ + "audits[image-redundant-alt].description" + ], + "core/audits/accessibility/input-button-name.js | title": [ + "audits[input-button-name].title" + ], + "core/audits/accessibility/input-button-name.js | description": [ + "audits[input-button-name].description" + ], + "core/audits/accessibility/input-image-alt.js | title": [ + "audits[input-image-alt].title" + ], + "core/audits/accessibility/input-image-alt.js | description": [ + "audits[input-image-alt].description" + ], + "core/audits/accessibility/label-content-name-mismatch.js | title": [ + "audits[label-content-name-mismatch].title" + ], + "core/audits/accessibility/label-content-name-mismatch.js | description": [ + "audits[label-content-name-mismatch].description" + ], + "core/audits/accessibility/label.js | title": ["audits.label.title"], + "core/audits/accessibility/label.js | description": [ + "audits.label.description" + ], + "core/audits/accessibility/landmark-one-main.js | title": [ + "audits[landmark-one-main].title" + ], + "core/audits/accessibility/landmark-one-main.js | description": [ + "audits[landmark-one-main].description" + ], + "core/audits/accessibility/link-name.js | title": [ + "audits[link-name].title" + ], + "core/audits/accessibility/link-name.js | description": [ + "audits[link-name].description" + ], + "core/audits/accessibility/link-in-text-block.js | title": [ + "audits[link-in-text-block].title" + ], + "core/audits/accessibility/link-in-text-block.js | description": [ + "audits[link-in-text-block].description" + ], + "core/audits/accessibility/list.js | title": ["audits.list.title"], + "core/audits/accessibility/list.js | description": [ + "audits.list.description" + ], + "core/audits/accessibility/listitem.js | title": [ + "audits.listitem.title" + ], + "core/audits/accessibility/listitem.js | description": [ + "audits.listitem.description" + ], + "core/audits/accessibility/meta-refresh.js | title": [ + "audits[meta-refresh].title" + ], + "core/audits/accessibility/meta-refresh.js | description": [ + "audits[meta-refresh].description" + ], + "core/audits/accessibility/meta-viewport.js | title": [ + "audits[meta-viewport].title" + ], + "core/audits/accessibility/meta-viewport.js | description": [ + "audits[meta-viewport].description" + ], + "core/audits/accessibility/object-alt.js | title": [ + "audits[object-alt].title" + ], + "core/audits/accessibility/object-alt.js | description": [ + "audits[object-alt].description" + ], + "core/audits/accessibility/select-name.js | title": [ + "audits[select-name].title" + ], + "core/audits/accessibility/select-name.js | description": [ + "audits[select-name].description" + ], + "core/audits/accessibility/skip-link.js | title": [ + "audits[skip-link].title" + ], + "core/audits/accessibility/skip-link.js | description": [ + "audits[skip-link].description" + ], + "core/audits/accessibility/tabindex.js | title": [ + "audits.tabindex.title" + ], + "core/audits/accessibility/tabindex.js | description": [ + "audits.tabindex.description" + ], + "core/audits/accessibility/table-duplicate-name.js | title": [ + "audits[table-duplicate-name].title" + ], + "core/audits/accessibility/table-duplicate-name.js | description": [ + "audits[table-duplicate-name].description" + ], + "core/audits/accessibility/table-fake-caption.js | title": [ + "audits[table-fake-caption].title" + ], + "core/audits/accessibility/table-fake-caption.js | description": [ + "audits[table-fake-caption].description" + ], + "core/audits/accessibility/target-size.js | title": [ + "audits[target-size].title" + ], + "core/audits/accessibility/target-size.js | description": [ + "audits[target-size].description" + ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], + "core/audits/accessibility/td-headers-attr.js | title": [ + "audits[td-headers-attr].title" + ], + "core/audits/accessibility/td-headers-attr.js | description": [ + "audits[td-headers-attr].description" + ], + "core/audits/accessibility/th-has-data-cells.js | title": [ + "audits[th-has-data-cells].title" + ], + "core/audits/accessibility/th-has-data-cells.js | description": [ + "audits[th-has-data-cells].description" + ], + "core/audits/accessibility/valid-lang.js | title": [ + "audits[valid-lang].title" + ], + "core/audits/accessibility/valid-lang.js | description": [ + "audits[valid-lang].description" + ], + "core/audits/accessibility/video-caption.js | title": [ + "audits[video-caption].title" + ], + "core/audits/accessibility/video-caption.js | description": [ + "audits[video-caption].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | title": [ + "audits[uses-long-cache-ttl].title" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | description": [ + "audits[uses-long-cache-ttl].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | displayValue": [ + { + "values": { + "itemCount": 0 + }, + "path": "audits[uses-long-cache-ttl].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnCacheTTL": [ + "audits[uses-long-cache-ttl].details.headings[1].label", + "audits[cache-insight].details.headings[1].label" + ], + "core/audits/byte-efficiency/total-byte-weight.js | title": [ + "audits[total-byte-weight].title" + ], + "core/audits/byte-efficiency/total-byte-weight.js | description": [ + "audits[total-byte-weight].description" + ], + "core/audits/byte-efficiency/total-byte-weight.js | displayValue": [ + { + "values": { + "totalBytes": 363395 + }, + "path": "audits[total-byte-weight].displayValue" + } + ], + "core/audits/byte-efficiency/offscreen-images.js | title": [ + "audits[offscreen-images].title" + ], + "core/audits/byte-efficiency/offscreen-images.js | description": [ + "audits[offscreen-images].description" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | title": [ + "audits[render-blocking-resources].title" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | description": [ + "audits[render-blocking-resources].description" + ], + "core/lib/i18n/i18n.js | displayValueMsSavings": [ + { + "values": { + "wastedMs": 240 + }, + "path": "audits[render-blocking-resources].displayValue" + }, + { + "values": { + "wastedMs": 240 + }, + "path": "audits[render-blocking-insight].displayValue" + } + ], + "core/audits/byte-efficiency/unminified-css.js | title": [ + "audits[unminified-css].title" + ], + "core/audits/byte-efficiency/unminified-css.js | description": [ + "audits[unminified-css].description" + ], + "core/audits/byte-efficiency/unminified-javascript.js | title": [ + "audits[unminified-javascript].title" + ], + "core/audits/byte-efficiency/unminified-javascript.js | description": [ + "audits[unminified-javascript].description" + ], + "core/audits/byte-efficiency/unused-css-rules.js | title": [ + "audits[unused-css-rules].title" + ], + "core/audits/byte-efficiency/unused-css-rules.js | description": [ + "audits[unused-css-rules].description" + ], + "core/audits/byte-efficiency/unused-javascript.js | title": [ + "audits[unused-javascript].title" + ], + "core/audits/byte-efficiency/unused-javascript.js | description": [ + "audits[unused-javascript].description" + ], + "core/lib/i18n/i18n.js | displayValueByteSavings": [ + { + "values": { + "wastedBytes": 61425 + }, + "path": "audits[unused-javascript].displayValue" + } + ], + "core/audits/byte-efficiency/modern-image-formats.js | title": [ + "audits[modern-image-formats].title" + ], + "core/audits/byte-efficiency/modern-image-formats.js | description": [ + "audits[modern-image-formats].description" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | title": [ + "audits[uses-optimized-images].title" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | description": [ + "audits[uses-optimized-images].description" + ], + "core/audits/byte-efficiency/uses-text-compression.js | title": [ + "audits[uses-text-compression].title" + ], + "core/audits/byte-efficiency/uses-text-compression.js | description": [ + "audits[uses-text-compression].description" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | title": [ + "audits[uses-responsive-images].title" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | description": [ + "audits[uses-responsive-images].description" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | title": [ + "audits[efficient-animated-content].title" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | description": [ + "audits[efficient-animated-content].description" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | title": [ + "audits[duplicated-javascript].title" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | description": [ + "audits[duplicated-javascript].description" + ], + "core/audits/byte-efficiency/legacy-javascript.js | title": [ + "audits[legacy-javascript].title" + ], + "core/audits/byte-efficiency/legacy-javascript.js | description": [ + "audits[legacy-javascript].description" + ], + "core/audits/dobetterweb/doctype.js | title": ["audits.doctype.title"], + "core/audits/dobetterweb/doctype.js | description": [ + "audits.doctype.description" + ], + "core/audits/dobetterweb/charset.js | title": ["audits.charset.title"], + "core/audits/dobetterweb/charset.js | description": [ + "audits.charset.description" + ], + "core/audits/dobetterweb/dom-size.js | title": ["audits[dom-size].title"], + "core/audits/dobetterweb/dom-size.js | description": [ + "audits[dom-size].description" + ], + "core/audits/dobetterweb/dom-size.js | displayValue": [ + { + "values": { + "itemCount": 100 + }, + "path": "audits[dom-size].displayValue" + } + ], + "core/audits/dobetterweb/dom-size.js | columnStatistic": [ + "audits[dom-size].details.headings[0].label" + ], + "core/audits/dobetterweb/dom-size.js | columnValue": [ + "audits[dom-size].details.headings[2].label" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMElements": [ + "audits[dom-size].details.items[0].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMDepth": [ + "audits[dom-size].details.items[1].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMWidth": [ + "audits[dom-size].details.items[2].statistic" + ], + "core/audits/dobetterweb/geolocation-on-start.js | title": [ + "audits[geolocation-on-start].title" + ], + "core/audits/dobetterweb/geolocation-on-start.js | description": [ + "audits[geolocation-on-start].description" + ], + "core/audits/dobetterweb/inspector-issues.js | title": [ + "audits[inspector-issues].title" + ], + "core/audits/dobetterweb/inspector-issues.js | description": [ + "audits[inspector-issues].description" + ], + "core/audits/dobetterweb/inspector-issues.js | columnIssueType": [ + "audits[inspector-issues].details.headings[0].label" + ], + "core/audits/dobetterweb/no-document-write.js | title": [ + "audits[no-document-write].title" + ], + "core/audits/dobetterweb/no-document-write.js | description": [ + "audits[no-document-write].description" + ], + "core/audits/dobetterweb/js-libraries.js | title": [ + "audits[js-libraries].title" + ], + "core/audits/dobetterweb/js-libraries.js | description": [ + "audits[js-libraries].description" + ], + "core/audits/dobetterweb/notification-on-start.js | title": [ + "audits[notification-on-start].title" + ], + "core/audits/dobetterweb/notification-on-start.js | description": [ + "audits[notification-on-start].description" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | title": [ + "audits[paste-preventing-inputs].title" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | description": [ + "audits[paste-preventing-inputs].description" + ], + "core/audits/dobetterweb/uses-http2.js | title": [ + "audits[uses-http2].title" + ], + "core/audits/dobetterweb/uses-http2.js | description": [ + "audits[uses-http2].description" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | title": [ + "audits[uses-passive-event-listeners].title" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | description": [ + "audits[uses-passive-event-listeners].description" + ], + "core/audits/seo/meta-description.js | title": [ + "audits[meta-description].title" + ], + "core/audits/seo/meta-description.js | description": [ + "audits[meta-description].description" + ], + "core/audits/seo/http-status-code.js | title": [ + "audits[http-status-code].title" + ], + "core/audits/seo/http-status-code.js | description": [ + "audits[http-status-code].description" + ], + "core/audits/seo/font-size.js | title": ["audits[font-size].title"], + "core/audits/seo/font-size.js | description": [ + "audits[font-size].description" + ], + "core/audits/seo/link-text.js | title": ["audits[link-text].title"], + "core/audits/seo/link-text.js | description": [ + "audits[link-text].description" + ], + "core/audits/seo/crawlable-anchors.js | title": [ + "audits[crawlable-anchors].title" + ], + "core/audits/seo/crawlable-anchors.js | description": [ + "audits[crawlable-anchors].description" + ], + "core/audits/seo/crawlable-anchors.js | columnFailingLink": [ + "audits[crawlable-anchors].details.headings[0].label" + ], + "core/audits/seo/is-crawlable.js | title": ["audits[is-crawlable].title"], + "core/audits/seo/is-crawlable.js | description": [ + "audits[is-crawlable].description" + ], + "core/audits/seo/robots-txt.js | failureTitle": [ + "audits[robots-txt].title" + ], + "core/audits/seo/robots-txt.js | description": [ + "audits[robots-txt].description" + ], + "core/audits/seo/robots-txt.js | displayValueValidationError": [ + { + "values": { + "itemCount": 48 + }, + "path": "audits[robots-txt].displayValue" + } + ], + "core/audits/seo/hreflang.js | title": ["audits.hreflang.title"], + "core/audits/seo/hreflang.js | description": [ + "audits.hreflang.description" + ], + "core/audits/seo/canonical.js | title": ["audits.canonical.title"], + "core/audits/seo/canonical.js | description": [ + "audits.canonical.description" + ], + "core/audits/seo/manual/structured-data.js | title": [ + "audits[structured-data].title" + ], + "core/audits/seo/manual/structured-data.js | description": [ + "audits[structured-data].description" + ], + "core/audits/bf-cache.js | title": ["audits[bf-cache].title"], + "core/audits/bf-cache.js | description": ["audits[bf-cache].description"], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | title": [ + "audits[cache-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | description": [ + "audits[cache-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | requestColumn": [ + "audits[cache-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "core/audits/insights/cls-culprits-insight.js | columnScore": [ + "audits[cls-culprits-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | unsizedImage": [ + "audits[cls-culprits-insight].details.items[0].items[2].subItems.items[0].cause" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": [ + "audits[document-latency-insight].details.items.noRedirects.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": [ + { + "values": { + "PH1": "15 ms" + }, + "path": "audits[document-latency-insight].details.items.serverResponseIsFast.label" + } + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingTextCompression": [ + "audits[document-latency-insight].details.items.usesCompression.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | title": [ + "audits[duplicated-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | description": [ + "audits[duplicated-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnSource": [ + "audits[duplicated-javascript-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnDuplicatedBytes": [ + "audits[duplicated-javascript-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | totalReflowTime": [ + "audits[forced-reflow-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "core/lib/i18n/i18n.js | columnResourceSize": [ + "audits[image-delivery-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | title": [ + "audits[inp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | description": [ + "audits[inp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | title": [ + "audits[lcp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | description": [ + "audits[lcp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | subpart": [ + "audits[lcp-breakdown-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | timeToFirstByte": [ + "audits[lcp-breakdown-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | elementRenderDelay": [ + "audits[lcp-breakdown-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | title": [ + "audits[legacy-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | description": [ + "audits[legacy-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | columnWastedBytes": [ + "audits[legacy-javascript-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | title": [ + "audits[modern-http-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | description": [ + "audits[modern-http-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | protocol": [ + "audits[modern-http-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | title": [ + "audits[network-dependency-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | description": [ + "audits[network-dependency-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableTitle": [ + "audits[network-dependency-tree-insight].details.items[1].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableDescription": [ + "audits[network-dependency-tree-insight].details.items[1].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectOrigins": [ + "audits[network-dependency-tree-insight].details.items[1].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableTitle": [ + "audits[network-dependency-tree-insight].details.items[2].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableDescription": [ + "audits[network-dependency-tree-insight].details.items[2].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectCandidates": [ + "audits[network-dependency-tree-insight].details.items[2].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": [ + "audits[third-parties-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": [ + "audits[third-parties-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnMainThreadTime": [ + "audits[third-parties-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], + "core/config/default-config.js | performanceCategoryTitle": [ + "categories.performance.title" + ], + "core/config/default-config.js | a11yCategoryTitle": [ + "categories.accessibility.title" + ], + "core/config/default-config.js | a11yCategoryDescription": [ + "categories.accessibility.description" + ], + "core/config/default-config.js | a11yCategoryManualDescription": [ + "categories.accessibility.manualDescription" + ], + "core/config/default-config.js | bestPracticesCategoryTitle": [ + "categories[best-practices].title" + ], + "core/config/default-config.js | seoCategoryTitle": [ + "categories.seo.title" + ], + "core/config/default-config.js | seoCategoryDescription": [ + "categories.seo.description" + ], + "core/config/default-config.js | seoCategoryManualDescription": [ + "categories.seo.manualDescription" + ], + "core/config/default-config.js | metricGroupTitle": [ + "categoryGroups.metrics.title" + ], + "core/config/default-config.js | insightsGroupTitle": [ + "categoryGroups.insights.title" + ], + "core/config/default-config.js | insightsGroupDescription": [ + "categoryGroups.insights.description" + ], + "core/config/default-config.js | diagnosticsGroupTitle": [ + "categoryGroups.diagnostics.title" + ], + "core/config/default-config.js | diagnosticsGroupDescription": [ + "categoryGroups.diagnostics.description" + ], + "core/config/default-config.js | a11yBestPracticesGroupTitle": [ + "categoryGroups[a11y-best-practices].title" + ], + "core/config/default-config.js | a11yBestPracticesGroupDescription": [ + "categoryGroups[a11y-best-practices].description" + ], + "core/config/default-config.js | a11yColorContrastGroupTitle": [ + "categoryGroups[a11y-color-contrast].title" + ], + "core/config/default-config.js | a11yColorContrastGroupDescription": [ + "categoryGroups[a11y-color-contrast].description" + ], + "core/config/default-config.js | a11yNamesLabelsGroupTitle": [ + "categoryGroups[a11y-names-labels].title" + ], + "core/config/default-config.js | a11yNamesLabelsGroupDescription": [ + "categoryGroups[a11y-names-labels].description" + ], + "core/config/default-config.js | a11yNavigationGroupTitle": [ + "categoryGroups[a11y-navigation].title" + ], + "core/config/default-config.js | a11yNavigationGroupDescription": [ + "categoryGroups[a11y-navigation].description" + ], + "core/config/default-config.js | a11yAriaGroupTitle": [ + "categoryGroups[a11y-aria].title" + ], + "core/config/default-config.js | a11yAriaGroupDescription": [ + "categoryGroups[a11y-aria].description" + ], + "core/config/default-config.js | a11yLanguageGroupTitle": [ + "categoryGroups[a11y-language].title" + ], + "core/config/default-config.js | a11yLanguageGroupDescription": [ + "categoryGroups[a11y-language].description" + ], + "core/config/default-config.js | a11yAudioVideoGroupTitle": [ + "categoryGroups[a11y-audio-video].title" + ], + "core/config/default-config.js | a11yAudioVideoGroupDescription": [ + "categoryGroups[a11y-audio-video].description" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupTitle": [ + "categoryGroups[a11y-tables-lists].title" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupDescription": [ + "categoryGroups[a11y-tables-lists].description" + ], + "core/config/default-config.js | seoMobileGroupTitle": [ + "categoryGroups[seo-mobile].title" + ], + "core/config/default-config.js | seoMobileGroupDescription": [ + "categoryGroups[seo-mobile].description" + ], + "core/config/default-config.js | seoContentGroupTitle": [ + "categoryGroups[seo-content].title" + ], + "core/config/default-config.js | seoContentGroupDescription": [ + "categoryGroups[seo-content].description" + ], + "core/config/default-config.js | seoCrawlingGroupTitle": [ + "categoryGroups[seo-crawl].title" + ], + "core/config/default-config.js | seoCrawlingGroupDescription": [ + "categoryGroups[seo-crawl].description" + ], + "core/config/default-config.js | bestPracticesTrustSafetyGroupTitle": [ + "categoryGroups[best-practices-trust-safety].title" + ], + "core/config/default-config.js | bestPracticesUXGroupTitle": [ + "categoryGroups[best-practices-ux].title" + ], + "core/config/default-config.js | bestPracticesBrowserCompatGroupTitle": [ + "categoryGroups[best-practices-browser-compat].title" + ], + "core/config/default-config.js | bestPracticesGeneralGroupTitle": [ + "categoryGroups[best-practices-general].title" + ] + } + } +} diff --git a/docs/performance/lighthouse-baseline-pre-busca-prod-mobile.json b/docs/performance/lighthouse-baseline-pre-busca-prod-mobile.json new file mode 100644 index 00000000..0494babf --- /dev/null +++ b/docs/performance/lighthouse-baseline-pre-busca-prod-mobile.json @@ -0,0 +1,10320 @@ +{ + "lighthouseVersion": "12.8.2", + "requestedUrl": "http://localhost:4173/", + "mainDocumentUrl": "http://localhost:4173/", + "finalDisplayedUrl": "http://localhost:4173/", + "finalUrl": "http://localhost:4173/", + "fetchTime": "2026-06-04T03:43:17.474Z", + "gatherMode": "navigation", + "runWarnings": [], + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "environment": { + "networkUserAgent": "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36", + "hostUserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.0.0 Safari/537.36", + "benchmarkIndex": 1219.5, + "credits": { + "axe-core": "4.12.0" + } + }, + "audits": { + "is-on-https": { + "id": "is-on-https", + "title": "Uses HTTPS", + "description": "All sites should be protected with HTTPS, even ones that don't handle sensitive data. This includes avoiding [mixed content](https://developers.google.com/web/fundamentals/security/prevent-mixed-content/what-is-mixed-content), where some resources are loaded over HTTP despite the initial request being served over HTTPS. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more about HTTPS](https://developer.chrome.com/docs/lighthouse/pwa/is-on-https/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Insecure URL" + }, + { + "key": "resolution", + "valueType": "text", + "label": "Request Resolution" + } + ], + "items": [] + } + }, + "redirects-http": { + "id": "redirects-http", + "title": "Redirects HTTP traffic to HTTPS", + "description": "Make sure that you redirect all HTTP traffic to HTTPS in order to enable secure web features for all your users. [Learn more](https://developer.chrome.com/docs/lighthouse/pwa/redirects-http/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "viewport": { + "id": "viewport", + "title": "Has a `` tag with `width` or `initial-scale`", + "description": "A `` not only optimizes your app for mobile screen sizes, but also prevents [a 300 millisecond delay to user input](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/). [Learn more about using the viewport meta tag](https://developer.chrome.com/docs/lighthouse/pwa/viewport/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "debugdata", + "viewportContent": "width=device-width, initial-scale=1.0" + }, + "guidanceLevel": 3 + }, + "first-contentful-paint": { + "id": "first-contentful-paint", + "title": "First Contentful Paint", + "description": "First Contentful Paint marks the time at which the first text or image is painted. [Learn more about the First Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint/).", + "score": 0.58, + "scoreDisplayMode": "numeric", + "numericValue": 2761.3151, + "numericUnit": "millisecond", + "displayValue": "2.8 s", + "scoringOptions": { + "p10": 1800, + "median": 3000 + } + }, + "largest-contentful-paint": { + "id": "largest-contentful-paint", + "title": "Largest Contentful Paint", + "description": "Largest Contentful Paint marks the time at which the largest text or image is painted. [Learn more about the Largest Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 0.74, + "scoreDisplayMode": "numeric", + "numericValue": 3139.4726499999997, + "numericUnit": "millisecond", + "displayValue": "3.1 s", + "scoringOptions": { + "p10": 2500, + "median": 4000 + } + }, + "first-meaningful-paint": { + "id": "first-meaningful-paint", + "title": "First Meaningful Paint", + "description": "First Meaningful Paint measures when the primary content of a page is visible. [Learn more about the First Meaningful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "speed-index": { + "id": "speed-index", + "title": "Speed Index", + "description": "Speed Index shows how quickly the contents of a page are visibly populated. [Learn more about the Speed Index metric](https://developer.chrome.com/docs/lighthouse/performance/speed-index/).", + "score": 0.96, + "scoreDisplayMode": "numeric", + "numericValue": 2761.3151, + "numericUnit": "millisecond", + "displayValue": "2.8 s", + "scoringOptions": { + "p10": 3387, + "median": 5800 + } + }, + "screenshot-thumbnails": { + "id": "screenshot-thumbnails", + "title": "Screenshot Thumbnails", + "description": "This is what the load of your site looked like.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "filmstrip", + "scale": 3000, + "items": [ + { + "timing": 375, + "timestamp": 27727474059, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 750, + "timestamp": 27727849059, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1125, + "timestamp": 27728224059, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1500, + "timestamp": 27728599059, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 1875, + "timestamp": 27728974059, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2250, + "timestamp": 27729349059, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 2625, + "timestamp": 27729724059, + "data": "data:image/jpeg;base64,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" + }, + { + "timing": 3000, + "timestamp": 27730099059, + "data": "data:image/jpeg;base64,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" + } + ] + } + }, + "final-screenshot": { + "id": "final-screenshot", + "title": "Final Screenshot", + "description": "The last screenshot captured of the pageload.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "screenshot", + "timing": 706, + "timestamp": 27727805267, + "data": "data:image/jpeg;base64,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" + } + }, + "total-blocking-time": { + "id": "total-blocking-time", + "title": "Total Blocking Time", + "description": "Sum of all time periods between FCP and Time to Interactive, when task length exceeded 50ms, expressed in milliseconds. [Learn more about the Total Blocking Time metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/).", + "score": 1, + "scoreDisplayMode": "numeric", + "numericValue": 30, + "numericUnit": "millisecond", + "displayValue": "30 ms", + "scoringOptions": { + "p10": 200, + "median": 600 + } + }, + "max-potential-fid": { + "id": "max-potential-fid", + "title": "Max Potential First Input Delay", + "description": "The maximum potential First Input Delay that your users could experience is the duration of the longest task. [Learn more about the Maximum Potential First Input Delay metric](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-max-potential-fid/).", + "score": 0.99, + "scoreDisplayMode": "numeric", + "numericValue": 80, + "numericUnit": "millisecond", + "displayValue": "80 ms" + }, + "cumulative-layout-shift": { + "id": "cumulative-layout-shift", + "title": "Cumulative Layout Shift", + "description": "Cumulative Layout Shift measures the movement of visible elements within the viewport. [Learn more about the Cumulative Layout Shift metric](https://web.dev/articles/cls).", + "score": 0.68, + "scoreDisplayMode": "numeric", + "numericValue": 0.176479, + "numericUnit": "unitless", + "displayValue": "0.176", + "scoringOptions": { + "p10": 0.1, + "median": 0.25 + }, + "details": { + "type": "debugdata", + "items": [ + { + "cumulativeLayoutShiftMainFrame": 0.176479, + "newEngineResultDiffered": false + } + ] + } + }, + "errors-in-console": { + "id": "errors-in-console", + "title": "Browser errors were logged to the console", + "description": "Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns. [Learn more about this errors in console diagnostic audit](https://developer.chrome.com/docs/lighthouse/best-practices/errors-in-console/)", + "score": 0, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "sourceLocation", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "description", + "valueType": "code", + "label": "Description" + } + ], + "items": [ + { + "source": "network", + "description": "Failed to load resource: net::ERR_ADDRESS_UNREACHABLE", + "sourceLocation": { + "type": "source-location", + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "urlProvider": "network", + "line": 0, + "column": 0 + } + }, + { + "source": "network", + "description": "Failed to load resource: net::ERR_CONNECTION_REFUSED", + "sourceLocation": { + "type": "source-location", + "url": "http://localhost:8000/api/v1/auth/me/", + "urlProvider": "network", + "line": 0, + "column": 0 + } + } + ] + } + }, + "server-response-time": { + "id": "server-response-time", + "title": "Initial server response time was short", + "description": "Keep the server response time for the main document short because all other requests depend on it. [Learn more about the Time to First Byte metric](https://developer.chrome.com/docs/lighthouse/performance/time-to-first-byte/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 1.468, + "numericUnit": "millisecond", + "displayValue": "Root document took 0 ms", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "responseTime", + "valueType": "timespanMs", + "label": "Time Spent" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "responseTime": 1.468 + } + ], + "overallSavingsMs": 0 + }, + "guidanceLevel": 1 + }, + "interactive": { + "id": "interactive", + "title": "Time to Interactive", + "description": "Time to Interactive is the amount of time it takes for the page to become fully interactive. [Learn more about the Time to Interactive metric](https://developer.chrome.com/docs/lighthouse/performance/interactive/).", + "score": 0.94, + "scoreDisplayMode": "numeric", + "numericValue": 3181.5517525, + "numericUnit": "millisecond", + "displayValue": "3.2 s" + }, + "user-timings": { + "id": "user-timings", + "title": "User Timing marks and measures", + "description": "Consider instrumenting your app with the User Timing API to measure your app's real-world performance during key user experiences. [Learn more about User Timing marks](https://developer.chrome.com/docs/lighthouse/performance/user-timings/).", + "score": null, + "scoreDisplayMode": "notApplicable", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "timingType", + "valueType": "text", + "label": "Type" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 0.01, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 0.01, + "label": "Duration" + } + ], + "items": [] + }, + "guidanceLevel": 2 + }, + "critical-request-chains": { + "id": "critical-request-chains", + "title": "Avoid chaining critical requests", + "description": "The Critical Request Chains below show you what resources are loaded with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load. [Learn how to avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "6 chains found", + "details": { + "type": "criticalrequestchain", + "chains": { + "EDBC1951D08BC8455EFB6D2E114058A0": { + "request": { + "url": "http://localhost:4173/", + "startTime": 27727.10364, + "endTime": 27727.106185000004, + "responseReceivedTime": 27727.105699, + "transferSize": 1284 + }, + "children": { + "187378.3": { + "request": { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "startTime": 27727.130616000002, + "endTime": 27727.138575, + "responseReceivedTime": 27727.136326, + "transferSize": 11584 + }, + "children": { + "187378.18": { + "request": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2", + "startTime": 27727.356866, + "endTime": 27727.364582, + "responseReceivedTime": 27727.360203999997, + "transferSize": 58404 + } + }, + "187378.15": { + "request": { + "url": "http://localhost:4173/assets/inter-latin-wght-normal-Dx4kXJAl.woff2", + "startTime": 27727.357188, + "endTime": 27727.364826, + "responseReceivedTime": 27727.361571, + "transferSize": 48576 + } + }, + "187378.23": { + "request": { + "url": "http://localhost:4173/assets/montserrat-latin-wght-normal-l_AIctKy.woff2", + "startTime": 27727.357381, + "endTime": 27727.36498, + "responseReceivedTime": 27727.362884, + "transferSize": 38276 + } + }, + "187378.26": { + "request": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2", + "startTime": 27727.357817, + "endTime": 27727.365158, + "responseReceivedTime": 27727.363520000003, + "transferSize": 64840 + } + } + } + }, + "187378.8": { + "request": { + "url": "http://localhost:4173/site.webmanifest", + "startTime": 27727.133424, + "endTime": 27727.138923, + "responseReceivedTime": 27727.137210999997, + "transferSize": 796 + } + }, + "187378.2": { + "request": { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "startTime": 27727.130288, + "endTime": 27727.154987, + "responseReceivedTime": 27727.137941, + "transferSize": 116210 + } + } + } + } + }, + "longestChain": { + "duration": 261.51799999922514, + "length": 3, + "transferSize": 64840 + } + }, + "guidanceLevel": 1 + }, + "redirects": { + "id": "redirects", + "title": "Avoid multiple page redirects", + "description": "Redirects introduce additional delays before the page can be loaded. [Learn how to avoid page redirects](https://developer.chrome.com/docs/lighthouse/performance/redirects/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0 + }, + "guidanceLevel": 2 + }, + "image-aspect-ratio": { + "id": "image-aspect-ratio", + "title": "Displays images with correct aspect ratio", + "description": "Image display dimensions should match natural aspect ratio. [Learn more about image aspect ratio](https://developer.chrome.com/docs/lighthouse/best-practices/image-aspect-ratio/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Displayed)" + }, + { + "key": "actualAspectRatio", + "valueType": "text", + "label": "Aspect Ratio (Actual)" + } + ], + "items": [] + } + }, + "image-size-responsive": { + "id": "image-size-responsive", + "title": "Serves images with appropriate resolution", + "description": "Image natural dimensions should be proportional to the display size and the pixel ratio to maximize image clarity. [Learn how to provide responsive images](https://web.dev/articles/serve-responsive-images).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "displayedSize", + "valueType": "text", + "label": "Displayed size" + }, + { + "key": "actualSize", + "valueType": "text", + "label": "Actual size" + }, + { + "key": "expectedSize", + "valueType": "text", + "label": "Expected size" + } + ], + "items": [] + } + }, + "deprecations": { + "id": "deprecations", + "title": "Avoids deprecated APIs", + "description": "Deprecated APIs will eventually be removed from the browser. [Learn more about deprecated APIs](https://developer.chrome.com/docs/lighthouse/best-practices/deprecations/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "value", + "valueType": "text", + "label": "Deprecation / Warning" + }, + { + "key": "source", + "valueType": "source-location", + "label": "Source" + } + ], + "items": [] + } + }, + "third-party-cookies": { + "id": "third-party-cookies", + "title": "Avoids third-party cookies", + "description": "Third-party cookies may be blocked in some contexts. [Learn more about preparing for third-party cookie restrictions](https://privacysandbox.google.com/cookies/prepare/overview).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "name", + "valueType": "text", + "label": "Name" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [] + } + }, + "mainthread-work-breakdown": { + "id": "mainthread-work-breakdown", + "title": "Minimizes main-thread work", + "description": "Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to minimize main-thread work](https://developer.chrome.com/docs/lighthouse/performance/mainthread-work-breakdown/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 1361.2440000000017, + "numericUnit": "millisecond", + "displayValue": "1.4 s", + "metricSavings": { + "TBT": 50 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "groupLabel", + "valueType": "text", + "label": "Category" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "group": "styleLayout", + "groupLabel": "Style & Layout", + "duration": 569.6400000000001 + }, + { + "group": "other", + "groupLabel": "Other", + "duration": 363.084000000001 + }, + { + "group": "scriptEvaluation", + "groupLabel": "Script Evaluation", + "duration": 308.94800000000066 + }, + { + "group": "paintCompositeRender", + "groupLabel": "Rendering", + "duration": 81.10799999999999 + }, + { + "group": "garbageCollection", + "groupLabel": "Garbage Collection", + "duration": 24.999999999999986 + }, + { + "group": "parseHTML", + "groupLabel": "Parse HTML & CSS", + "duration": 7.46 + }, + { + "group": "scriptParseCompile", + "groupLabel": "Script Parsing & Compilation", + "duration": 6.004 + } + ], + "sortedBy": ["duration"] + }, + "guidanceLevel": 1 + }, + "bootup-time": { + "id": "bootup-time", + "title": "JavaScript execution time", + "description": "Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn how to reduce Javascript execution time](https://developer.chrome.com/docs/lighthouse/performance/bootup-time/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 311.1200000000007, + "numericUnit": "millisecond", + "displayValue": "0.3 s", + "metricSavings": { + "TBT": 50 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "total", + "granularity": 1, + "valueType": "ms", + "label": "Total CPU Time" + }, + { + "key": "scripting", + "granularity": 1, + "valueType": "ms", + "label": "Script Evaluation" + }, + { + "key": "scriptParseCompile", + "granularity": 1, + "valueType": "ms", + "label": "Script Parse" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "total": 621.128, + "scripting": 15.104000000000001, + "scriptParseCompile": 3.4360000000000004 + }, + { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "total": 373.1640000000007, + "scripting": 280.5200000000007, + "scriptParseCompile": 2.568 + }, + { + "url": "Unattributable", + "total": 356.6280000000008, + "scripting": 9.492, + "scriptParseCompile": 0 + } + ], + "summary": { + "wastedMs": 311.1200000000007 + }, + "sortedBy": ["total"] + }, + "guidanceLevel": 1 + }, + "uses-rel-preconnect": { + "id": "uses-rel-preconnect", + "title": "Preconnect to required origins", + "description": "Consider adding `preconnect` or `dns-prefetch` resource hints to establish early connections to important third-party origins. [Learn how to preconnect to required origins](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "numericValue": 0, + "numericUnit": "millisecond", + "displayValue": "", + "warnings": [], + "metricSavings": { + "LCP": 0, + "FCP": 0 + }, + "details": { + "type": "opportunity", + "headings": [], + "items": [], + "overallSavingsMs": 0, + "sortedBy": ["wastedMs"] + }, + "guidanceLevel": 3 + }, + "font-display": { + "id": "font-display", + "title": "All text remains visible during webfont loads", + "description": "Leverage the `font-display` CSS feature to ensure text is user-visible while webfonts are loading. [Learn more about `font-display`](https://developer.chrome.com/docs/lighthouse/performance/font-display/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "warnings": [], + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "diagnostics": { + "id": "diagnostics", + "title": "Diagnostics", + "description": "Collection of useful page vitals.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "debugdata", + "items": [ + { + "numRequests": 15, + "numScripts": 2, + "numStylesheets": 1, + "numFonts": 4, + "numTasks": 757, + "numTasksOver10ms": 8, + "numTasksOver25ms": 2, + "numTasksOver50ms": 1, + "numTasksOver100ms": 1, + "numTasksOver500ms": 0, + "rtt": 0.05145, + "throughput": 94290347.66731822, + "maxRtt": 0.05145, + "maxServerLatency": 3.15755, + "totalByteWeight": 363395, + "totalTaskTime": 340.31099999999753, + "mainDocumentTransferSize": 1284 + } + ] + } + }, + "network-requests": { + "id": "network-requests", + "title": "Network Requests", + "description": "Lists the network requests that were made during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + }, + { + "key": "networkRequestTime", + "valueType": "ms", + "granularity": 1, + "label": "Network Request Time" + }, + { + "key": "networkEndTime", + "valueType": "ms", + "granularity": 1, + "label": "Network End Time" + }, + { + "key": "transferSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Transfer Size" + }, + { + "key": "resourceSize", + "valueType": "bytes", + "displayUnit": "kb", + "granularity": 1, + "label": "Resource Size" + }, + { + "key": "statusCode", + "valueType": "text", + "label": "Status Code" + }, + { + "key": "mimeType", + "valueType": "text", + "label": "MIME Type" + }, + { + "key": "resourceType", + "valueType": "text", + "label": "Resource Type" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 0, + "networkRequestTime": 2.014000002294779, + "networkEndTime": 4.559000004082918, + "finished": true, + "transferSize": 1284, + "resourceSize": 2173, + "statusCode": 200, + "mimeType": "text/html", + "resourceType": "Document", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 20.51400000229478, + "networkRequestTime": 28.662000000476837, + "networkEndTime": 53.3610000051558, + "finished": true, + "transferSize": 116210, + "resourceSize": 371869, + "statusCode": 200, + "mimeType": "text/javascript", + "resourceType": "Script", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 20.833000000566244, + "networkRequestTime": 28.990000002086163, + "networkEndTime": 36.949000000953674, + "finished": true, + "transferSize": 11584, + "resourceSize": 66490, + "statusCode": 200, + "mimeType": "text/css", + "resourceType": "Stylesheet", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 20.97700000181794, + "networkRequestTime": 20.97700000181794, + "networkEndTime": 87.68100000172853, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "Script", + "priority": "Low", + "experimentalFromMainFrame": true, + "entity": "vlibras.gov.br" + }, + { + "url": "http://localhost:4173/site.webmanifest", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 31.23200000077486, + "networkRequestTime": 31.798000000417233, + "networkEndTime": 37.297000002115965, + "finished": true, + "transferSize": 796, + "resourceSize": 517, + "statusCode": 200, + "mimeType": "application/manifest+json", + "resourceType": "Manifest", + "priority": "Medium", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 123.9620000012219, + "networkRequestTime": 124.56900000199676, + "networkEndTime": 132.617000002414, + "finished": true, + "transferSize": 22614, + "resourceSize": 116344, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "data:image/svg+xml,%3csvg%20id='Camada_1'%20data-name='Camada%201'%20xmlns='http://www.w3.org/2000/…", + "sessionTargetType": "page", + "protocol": "data", + "rendererStartTime": 128.22600000351667, + "networkRequestTime": 128.22600000351667, + "networkEndTime": 128.62000000104308, + "finished": true, + "transferSize": 0, + "resourceSize": 1581, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Image", + "priority": "Low", + "experimentalFromMainFrame": true + }, + { + "url": "http://localhost:4173/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 136.71099999919534, + "networkRequestTime": 255.23999999836087, + "networkEndTime": 262.9560000002384, + "finished": true, + "transferSize": 58404, + "resourceSize": 58084, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/inter-latin-wght-normal-Dx4kXJAl.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 152.26999999955297, + "networkRequestTime": 255.562000002712, + "networkEndTime": 263.20000000298023, + "finished": true, + "transferSize": 48576, + "resourceSize": 48256, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/montserrat-latin-wght-normal-l_AIctKy.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 163.0159999988973, + "networkRequestTime": 255.7550000026822, + "networkEndTime": 263.3539999984205, + "finished": true, + "transferSize": 38276, + "resourceSize": 37956, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 163.19900000095367, + "networkRequestTime": 256.19100000336766, + "networkEndTime": 263.5320000015199, + "finished": true, + "transferSize": 64840, + "resourceSize": 64520, + "statusCode": 200, + "mimeType": "font/woff2", + "resourceType": "Font", + "priority": "VeryHigh", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 285.84899999946356, + "networkRequestTime": 285.84899999946356, + "networkEndTime": 308.23200000077486, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:8000/api/v1/auth/me/", + "sessionTargetType": "page", + "protocol": "", + "rendererStartTime": 287.90100000053644, + "networkRequestTime": 287.90100000053644, + "networkEndTime": 309.089000005275, + "finished": true, + "transferSize": 0, + "resourceSize": 0, + "statusCode": -1, + "mimeType": "", + "resourceType": "XHR", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 306.52800000086427, + "networkRequestTime": 307.07999999821186, + "networkEndTime": 309.19999999925494, + "finished": true, + "transferSize": 684, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + }, + { + "url": "http://localhost:4173/interpop-icon.svg", + "sessionTargetType": "page", + "protocol": "http/1.1", + "rendererStartTime": 312.1160000041127, + "networkRequestTime": 313.1680000014603, + "networkEndTime": 317.59100000187755, + "finished": true, + "transferSize": 127, + "resourceSize": 417, + "statusCode": 200, + "mimeType": "image/svg+xml", + "resourceType": "Other", + "priority": "High", + "experimentalFromMainFrame": true, + "entity": "localhost" + } + ], + "debugData": { + "type": "debugdata", + "networkStartTimeTs": 27727101626, + "initiators": [ + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 24, + "columnNumber": 70 + }, + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 25, + "columnNumber": 73 + }, + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 39, + "columnNumber": 69 + }, + { + "type": "parser", + "url": "http://localhost:4173/", + "lineNumber": 45, + "columnNumber": 13 + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + }, + { + "type": "parser", + "url": "http://localhost:4173/assets/index-DAWkbRUl.css" + } + ] + } + } + }, + "network-rtt": { + "id": "network-rtt", + "title": "Network Round Trip Times", + "description": "Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more about the Round Trip Time](https://hpbn.co/primer-on-latency-and-bandwidth/).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 0.05145, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "rtt", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:4173", + "rtt": 0.05145 + } + ], + "sortedBy": ["rtt"] + } + }, + "network-server-latency": { + "id": "network-server-latency", + "title": "Server Backend Latencies", + "description": "Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more about server response time](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 3.15755, + "numericUnit": "millisecond", + "displayValue": "0 ms", + "details": { + "type": "table", + "headings": [ + { + "key": "origin", + "valueType": "text", + "label": "URL" + }, + { + "key": "serverResponseTime", + "valueType": "ms", + "granularity": 1, + "label": "Time Spent" + } + ], + "items": [ + { + "origin": "http://localhost:4173", + "serverResponseTime": 3.15755 + } + ], + "sortedBy": ["serverResponseTime"] + } + }, + "main-thread-tasks": { + "id": "main-thread-tasks", + "title": "Tasks", + "description": "Lists the toplevel main thread tasks that executed during page load.", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "End Time" + } + ], + "items": [ + { + "duration": 13.73, + "startTime": 10.506 + }, + { + "duration": 12.956, + "startTime": 67.185 + }, + { + "duration": 9.976, + "startTime": 80.151 + }, + { + "duration": 39.889, + "startTime": 92.101 + }, + { + "duration": 105.64, + "startTime": 132.313 + }, + { + "duration": 19.329, + "startTime": 237.992 + }, + { + "duration": 17.649, + "startTime": 257.768 + }, + { + "duration": 9.254, + "startTime": 281.96 + }, + { + "duration": 10.323, + "startTime": 292.267 + }, + { + "duration": 5.467, + "startTime": 303.449 + }, + { + "duration": 8.918, + "startTime": 315.143 + }, + { + "duration": 17.242, + "startTime": 324.072 + }, + { + "duration": 6.458, + "startTime": 341.418 + }, + { + "duration": 7.829, + "startTime": 354.791 + } + ] + } + }, + "metrics": { + "id": "metrics", + "title": "Metrics", + "description": "Collects all available metrics.", + "score": 1, + "scoreDisplayMode": "informative", + "numericValue": 3182, + "numericUnit": "millisecond", + "details": { + "type": "debugdata", + "items": [ + { + "firstContentfulPaint": 2761, + "largestContentfulPaint": 3139, + "interactive": 3182, + "speedIndex": 2761, + "totalBlockingTime": 30, + "maxPotentialFID": 80, + "cumulativeLayoutShift": 0.176479, + "cumulativeLayoutShiftMainFrame": 0.176479, + "timeToFirstByte": 453, + "observedTimeOrigin": 0, + "observedTimeOriginTs": 27727099059, + "observedNavigationStart": 0, + "observedNavigationStartTs": 27727099059, + "observedFirstPaint": 55, + "observedFirstPaintTs": 27727154254, + "observedFirstContentfulPaint": 290, + "observedFirstContentfulPaintTs": 27727388718, + "observedFirstContentfulPaintAllFrames": 290, + "observedFirstContentfulPaintAllFramesTs": 27727388718, + "observedLargestContentfulPaint": 290, + "observedLargestContentfulPaintTs": 27727388718, + "observedLargestContentfulPaintAllFrames": 290, + "observedLargestContentfulPaintAllFramesTs": 27727388718, + "observedTraceEnd": 2669, + "observedTraceEndTs": 27729768049, + "observedLoad": 91, + "observedLoadTs": 27727189823, + "observedDomContentLoaded": 91, + "observedDomContentLoadedTs": 27727189698, + "observedCumulativeLayoutShift": 0.176479, + "observedCumulativeLayoutShiftMainFrame": 0.176479, + "observedFirstVisualChange": 55, + "observedFirstVisualChangeTs": 27727154059, + "observedLastVisualChange": 688, + "observedLastVisualChangeTs": 27727787059, + "observedSpeedIndex": 136, + "observedSpeedIndexTs": 27727234946 + }, + { + "lcpInvalidated": false + } + ] + } + }, + "resource-summary": { + "id": "resource-summary", + "title": "Resources Summary", + "description": "Aggregates all network requests and groups them by type", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Resource Type" + }, + { + "key": "requestCount", + "valueType": "numeric", + "label": "Requests" + }, + { + "key": "transferSize", + "valueType": "bytes", + "label": "Transfer Size" + } + ], + "items": [ + { + "resourceType": "total", + "label": "Total", + "requestCount": 14, + "transferSize": 363395 + }, + { + "resourceType": "font", + "label": "Font", + "requestCount": 4, + "transferSize": 210096 + }, + { + "resourceType": "script", + "label": "Script", + "requestCount": 2, + "transferSize": 116210 + }, + { + "resourceType": "image", + "label": "Image", + "requestCount": 1, + "transferSize": 22614 + }, + { + "resourceType": "stylesheet", + "label": "Stylesheet", + "requestCount": 1, + "transferSize": 11584 + }, + { + "resourceType": "other", + "label": "Other", + "requestCount": 5, + "transferSize": 1607 + }, + { + "resourceType": "document", + "label": "Document", + "requestCount": 1, + "transferSize": 1284 + }, + { + "resourceType": "media", + "label": "Media", + "requestCount": 0, + "transferSize": 0 + }, + { + "resourceType": "third-party", + "label": "Third-party", + "requestCount": 1, + "transferSize": 0 + } + ] + } + }, + "third-party-summary": { + "id": "third-party-summary", + "title": "Minimize third-party usage", + "description": "Third-party code can significantly impact load performance. Limit the number of redundant third-party providers and try to load third-party code after your page has primarily finished loading. [Learn how to minimize third-party impact](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/).", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "Third-party code blocked the main thread for 0 ms", + "metricSavings": { + "TBT": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "Third-Party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer Size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "blockingTime", + "granularity": 1, + "valueType": "ms", + "label": "Main-Thread Blocking Time", + "subItemsHeading": { + "key": "blockingTime" + } + } + ], + "items": [ + { + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0, + "entity": "vlibras.gov.br", + "subItems": { + "type": "subitems", + "items": [ + { + "url": "https://vlibras.gov.br/app/vlibras-plugin.js", + "mainThreadTime": 0, + "blockingTime": 0, + "transferSize": 0, + "tbtImpact": 0 + } + ] + } + } + ], + "isEntityGrouped": true + }, + "guidanceLevel": 1 + }, + "third-party-facades": { + "id": "third-party-facades", + "title": "Lazy load third-party resources with facades", + "description": "Some third-party embeds can be lazy loaded. Consider replacing them with a facade until they are required. [Learn how to defer third-parties with a facade](https://developer.chrome.com/docs/lighthouse/performance/third-party-facades/).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "TBT": 0 + }, + "guidanceLevel": 3 + }, + "largest-contentful-paint-element": { + "id": "largest-contentful-paint-element", + "title": "Largest Contentful Paint element", + "description": "This is the largest contentful element painted within the viewport. [Learn more about the Largest Contentful Paint element](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint/)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "3,140 ms", + "metricSavings": { + "LCP": 650 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "Element" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 136, + "bottom": 341, + "left": 24, + "right": 388, + "width": 364, + "height": 205 + }, + "snippet": "

        ", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + } + ] + }, + { + "type": "table", + "headings": [ + { + "key": "phase", + "valueType": "text", + "label": "Phase" + }, + { + "key": "percent", + "valueType": "text", + "label": "% of LCP" + }, + { + "key": "timing", + "valueType": "ms", + "label": "Timing" + } + ], + "items": [ + { + "phase": "TTFB", + "timing": 453.15755, + "percent": "14%" + }, + { + "phase": "Load Delay", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Load Time", + "timing": 0, + "percent": "0%" + }, + { + "phase": "Render Delay", + "timing": 2686.3151, + "percent": "86%" + } + ] + } + ] + }, + "guidanceLevel": 1 + }, + "lcp-lazy-loaded": { + "id": "lcp-lazy-loaded", + "title": "Largest Contentful Paint image was not lazily loaded", + "description": "Above-the-fold images that are lazily loaded render later in the page lifecycle, which can delay the largest contentful paint. [Learn more about optimal lazy loading](https://web.dev/articles/lcp-lazy-loading).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 3 + }, + "layout-shifts": { + "id": "layout-shifts", + "title": "Avoid large layout shifts", + "description": "These are the largest layout shifts observed on the page. Each table item represents a single layout shift, and shows the element that shifted the most. Below each item are possible root causes that led to the layout shift. Some of these layout shifts may not be included in the CLS metric value due to [windowing](https://web.dev/articles/cls#what_is_cls). [Learn how to improve CLS](https://web.dev/articles/optimize-cls)", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "1 layout shift found", + "metricSavings": { + "CLS": 0.176 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-1-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV", + "selector": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual", + "boundingRect": { + "top": 494, + "bottom": 674, + "left": 24, + "right": 388, + "width": 364, + "height": 180 + }, + "snippet": "
        ", + "nodeLabel": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual" + }, + "score": 0.176479 + } + ] + }, + "guidanceLevel": 2 + }, + "long-tasks": { + "id": "long-tasks", + "title": "Avoid long main-thread tasks", + "description": "Lists the longest tasks on the main thread, useful for identifying worst contributors to input delay. [Learn how to avoid long main-thread tasks](https://web.dev/articles/optimize-long-tasks)", + "score": 1, + "scoreDisplayMode": "informative", + "displayValue": "4 long tasks found", + "metricSavings": { + "TBT": 50 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "startTime", + "valueType": "ms", + "granularity": 1, + "label": "Start Time" + }, + { + "key": "duration", + "valueType": "ms", + "granularity": 1, + "label": "Duration" + } + ], + "items": [ + { + "url": "http://localhost:4173/", + "duration": 211, + "startTime": 719.15755 + }, + { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "duration": 80, + "startTime": 3061.3151 + }, + { + "url": "Unattributable", + "duration": 55, + "startTime": 603.15755 + }, + { + "url": "Unattributable", + "duration": 52, + "startTime": 667.15755 + } + ], + "sortedBy": ["duration"], + "skipSumming": ["startTime"], + "debugData": { + "type": "debugdata", + "urls": [ + "http://localhost:4173/", + "http://localhost:4173/assets/index-BXwNXqtB.js", + "Unattributable" + ], + "tasks": [ + { + "urlIndex": 0, + "startTime": 719.2, + "duration": 211, + "other": 211, + "styleLayout": 0 + }, + { + "urlIndex": 1, + "startTime": 3061.3, + "duration": 80, + "other": 80 + }, + { + "urlIndex": 2, + "startTime": 603.2, + "duration": 55, + "other": 55, + "scriptEvaluation": 0 + }, + { + "urlIndex": 2, + "startTime": 667.2, + "duration": 52, + "other": 52 + } + ] + } + }, + "guidanceLevel": 1 + }, + "non-composited-animations": { + "id": "non-composited-animations", + "title": "Avoid non-composited animations", + "description": "Animations which are not composited can be janky and increase CLS. [Learn how to avoid non-composited animations](https://developer.chrome.com/docs/lighthouse/performance/non-composited-animations/)", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "failureReason", + "valueType": "text" + }, + "label": "Element" + } + ], + "items": [] + }, + "guidanceLevel": 2 + }, + "unsized-images": { + "id": "unsized-images", + "title": "Image elements do not have explicit `width` and `height`", + "description": "Set an explicit width and height on image elements to reduce layout shifts and improve CLS. [Learn how to set image dimensions](https://web.dev/articles/optimize-cls#images_without_dimensions)", + "score": 0.5, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + }, + { + "key": "url", + "valueType": "url", + "label": "URL" + } + ], + "items": [ + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "node": { + "type": "node", + "lhId": "1-27-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,1,HEADER,0,DIV,0,A,0,IMG", + "selector": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img", + "boundingRect": { + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "snippet": "\"\"", + "nodeLabel": "header.navbar > div.navbar__inner > a.navbar__logo > img.navbar__logo-img" + } + }, + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "node": { + "type": "node", + "lhId": "1-28-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV,1,IMG", + "selector": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark", + "boundingRect": { + "top": 552, + "bottom": 616, + "left": 96, + "right": 316, + "width": 220, + "height": 64 + }, + "snippet": "\"\"", + "nodeLabel": "div.container > div.home-hero__grid > div.home-hero__visual > img.home-hero__visual-mark" + } + }, + { + "url": "http://localhost:4173/assets/interpop-logo-CHzKRfhf.svg", + "node": { + "type": "node", + "lhId": "1-29-IMG", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,1,DIV,0,A,0,IMG", + "selector": "footer.footer > div.footer__bottom > a.footer__logo > img", + "boundingRect": { + "top": 1800, + "bottom": 1840, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "snippet": "\"\"", + "nodeLabel": "footer.footer > div.footer__bottom > a.footer__logo > img" + } + } + ] + }, + "guidanceLevel": 4 + }, + "valid-source-maps": { + "id": "valid-source-maps", + "title": "Page has valid source maps", + "description": "Source maps translate minified code to the original source code. This helps developers debug in production. In addition, Lighthouse is able to provide further insights. Consider deploying source maps to take advantage of these benefits. [Learn more about source maps](https://developer.chrome.com/docs/devtools/javascript/source-maps/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "scriptUrl", + "valueType": "url", + "subItemsHeading": { + "key": "error" + }, + "label": "URL" + }, + { + "key": "sourceMapUrl", + "valueType": "url", + "label": "Map URL" + } + ], + "items": [] + } + }, + "prioritize-lcp-image": { + "id": "prioritize-lcp-image", + "title": "Preload Largest Contentful Paint image", + "description": "If the LCP element is dynamically added to the page, you should preload the image in order to improve LCP. [Learn more about preloading LCP elements](https://web.dev/articles/optimize-lcp#optimize_when_the_resource_is_discovered).", + "score": null, + "scoreDisplayMode": "notApplicable", + "metricSavings": { + "LCP": 0 + }, + "guidanceLevel": 4 + }, + "csp-xss": { + "id": "csp-xss", + "title": "Ensure CSP is effective against XSS attacks", + "description": "A strong Content Security Policy (CSP) significantly reduces the risk of cross-site scripting (XSS) attacks. [Learn how to use a CSP to prevent XSS](https://developer.chrome.com/docs/lighthouse/best-practices/csp-xss/)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No CSP found in enforcement mode" + } + ] + } + }, + "has-hsts": { + "id": "has-hsts", + "title": "Use a strong HSTS policy", + "description": "Deployment of the HSTS header significantly reduces the risk of downgrading HTTP connections and eavesdropping attacks. A rollout in stages, starting with a low max-age is recommended. [Learn more about using a strong HSTS policy.](https://developer.chrome.com/docs/lighthouse/best-practices/has-hsts)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No HSTS header found" + } + ] + } + }, + "origin-isolation": { + "id": "origin-isolation", + "title": "Ensure proper origin isolation with COOP", + "description": "The Cross-Origin-Opener-Policy (COOP) can be used to isolate the top-level window from other documents such as pop-ups. [Learn more about deploying the COOP header.](https://web.dev/articles/why-coop-coep#coop)", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "directive", + "valueType": "code", + "subItemsHeading": { + "key": "directive" + }, + "label": "Directive" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "description": "No COOP header found", + "severity": "High" + } + ] + } + }, + "clickjacking-mitigation": { + "id": "clickjacking-mitigation", + "title": "Mitigate clickjacking with XFO or CSP", + "description": "The `X-Frame-Options` (XFO) header or the `frame-ancestors` directive in the `Content-Security-Policy` (CSP) header control where a page can be embedded. These can mitigate clickjacking attacks by blocking some or all sites from embedding the page. [Learn more about mitigating clickjacking](https://developer.chrome.com/docs/lighthouse/best-practices/clickjacking-mitigation).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No frame control policy found" + } + ] + } + }, + "trusted-types-xss": { + "id": "trusted-types-xss", + "title": "Mitigate DOM-based XSS with Trusted Types", + "description": "The `require-trusted-types-for` directive in the `Content-Security-Policy` (CSP) header instructs user agents to control the data passed to DOM XSS sink functions. [Learn more about mitigating DOM-based XSS with Trusted Types](https://developer.chrome.com/docs/lighthouse/best-practices/trusted-types-xss).", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "table", + "headings": [ + { + "key": "description", + "valueType": "text", + "subItemsHeading": { + "key": "description" + }, + "label": "Description" + }, + { + "key": "severity", + "valueType": "text", + "subItemsHeading": { + "key": "severity" + }, + "label": "Severity" + } + ], + "items": [ + { + "severity": "High", + "description": "No `Content-Security-Policy` header with Trusted Types directive found" + } + ] + } + }, + "script-treemap-data": { + "id": "script-treemap-data", + "title": "Script Treemap Data", + "description": "Used for treemap app", + "score": 1, + "scoreDisplayMode": "informative", + "details": { + "type": "treemap-data", + "nodes": [ + { + "name": "http://localhost:4173/", + "resourceBytes": 168, + "encodedBytes": 79, + "children": [ + { + "name": "(inline) window.addEvent…", + "resourceBytes": 168, + "unusedBytes": 0 + } + ] + }, + { + "name": "http://localhost:4173/assets/index-BXwNXqtB.js", + "resourceBytes": 371087, + "encodedBytes": 115855, + "unusedBytes": 197160 + } + ] + } + }, + "accesskeys": { + "id": "accesskeys", + "title": "`[accesskey]` values are unique", + "description": "Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more about access keys](https://dequeuniversity.com/rules/axe/4.10/accesskeys).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-allowed-attr": { + "id": "aria-allowed-attr", + "title": "`[aria-*]` attributes match their roles", + "description": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn how to match ARIA attributes to their roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-allowed-role": { + "id": "aria-allowed-role", + "title": "Uses ARIA roles only on compatible elements", + "description": "Many HTML elements can only be assigned certain ARIA roles. Using ARIA roles where they are not allowed can interfere with the accessibility of the web page. [Learn more about ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-allowed-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-command-name": { + "id": "aria-command-name", + "title": "`button`, `link`, and `menuitem` elements have accessible names", + "description": "When an element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to make command elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-command-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-conditional-attr": { + "id": "aria-conditional-attr", + "title": "ARIA attributes are used as specified for the element's role", + "description": "Some ARIA attributes are only allowed on an element under certain conditions. [Learn more about conditional ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-conditional-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-deprecated-role": { + "id": "aria-deprecated-role", + "title": "Deprecated ARIA roles were not used", + "description": "Deprecated ARIA roles may not be processed correctly by assistive technology. [Learn more about deprecated ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-deprecated-role).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-dialog-name": { + "id": "aria-dialog-name", + "title": "Elements with `role=\"dialog\"` or `role=\"alertdialog\"` have accessible names.", + "description": "ARIA dialog elements without accessible names may prevent screen readers users from discerning the purpose of these elements. [Learn how to make ARIA dialog elements more accessible](https://dequeuniversity.com/rules/axe/4.10/aria-dialog-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-hidden-body": { + "id": "aria-hidden-body", + "title": "`[aria-hidden=\"true\"]` is not present on the document ``", + "description": "Assistive technologies, like screen readers, work inconsistently when `aria-hidden=\"true\"` is set on the document ``. [Learn how `aria-hidden` affects the document body](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-body).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-hidden-focus": { + "id": "aria-hidden-focus", + "title": "`[aria-hidden=\"true\"]` elements do not contain focusable descendents", + "description": "Focusable descendents within an `[aria-hidden=\"true\"]` element prevent those interactive elements from being available to users of assistive technologies like screen readers. [Learn how `aria-hidden` affects focusable elements](https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-input-field-name": { + "id": "aria-input-field-name", + "title": "ARIA input fields have accessible names", + "description": "When an input field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about input field labels](https://dequeuniversity.com/rules/axe/4.10/aria-input-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-meter-name": { + "id": "aria-meter-name", + "title": "ARIA `meter` elements have accessible names", + "description": "When a meter element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `meter` elements](https://dequeuniversity.com/rules/axe/4.10/aria-meter-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-progressbar-name": { + "id": "aria-progressbar-name", + "title": "ARIA `progressbar` elements have accessible names", + "description": "When a `progressbar` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to label `progressbar` elements](https://dequeuniversity.com/rules/axe/4.10/aria-progressbar-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-prohibited-attr": { + "id": "aria-prohibited-attr", + "title": "Elements use only permitted ARIA attributes", + "description": "Using ARIA attributes in roles where they are prohibited can mean that important information is not communicated to users of assistive technologies. [Learn more about prohibited ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-prohibited-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-attr": { + "id": "aria-required-attr", + "title": "`[role]`s have all required `[aria-*]` attributes", + "description": "Some ARIA roles have required attributes that describe the state of the element to screen readers. [Learn more about roles and required attributes](https://dequeuniversity.com/rules/axe/4.10/aria-required-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-required-children": { + "id": "aria-required-children", + "title": "Elements with an ARIA `[role]` that require children to contain a specific `[role]` have all required children.", + "description": "Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions. [Learn more about roles and required children elements](https://dequeuniversity.com/rules/axe/4.10/aria-required-children).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-required-parent": { + "id": "aria-required-parent", + "title": "`[role]`s are contained by their required parent element", + "description": "Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions. [Learn more about ARIA roles and required parent element](https://dequeuniversity.com/rules/axe/4.10/aria-required-parent).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-roles": { + "id": "aria-roles", + "title": "`[role]` values are valid", + "description": "ARIA roles must have valid values in order to perform their intended accessibility functions. [Learn more about valid ARIA roles](https://dequeuniversity.com/rules/axe/4.10/aria-roles).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-text": { + "id": "aria-text", + "title": "Elements with the `role=text` attribute do not have focusable descendents.", + "description": "Adding `role=text` around a text node split by markup enables VoiceOver to treat it as one phrase, but the element's focusable descendents will not be announced. [Learn more about the `role=text` attribute](https://dequeuniversity.com/rules/axe/4.10/aria-text).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-toggle-field-name": { + "id": "aria-toggle-field-name", + "title": "ARIA toggle fields have accessible names", + "description": "When a toggle field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about toggle fields](https://dequeuniversity.com/rules/axe/4.10/aria-toggle-field-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-tooltip-name": { + "id": "aria-tooltip-name", + "title": "ARIA `tooltip` elements have accessible names", + "description": "When a tooltip element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn how to name `tooltip` elements](https://dequeuniversity.com/rules/axe/4.10/aria-tooltip-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-treeitem-name": { + "id": "aria-treeitem-name", + "title": "ARIA `treeitem` elements have accessible names", + "description": "When a `treeitem` element doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. [Learn more about labeling `treeitem` elements](https://dequeuniversity.com/rules/axe/4.10/aria-treeitem-name).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "aria-valid-attr-value": { + "id": "aria-valid-attr-value", + "title": "`[aria-*]` attributes have valid values", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more about valid values for ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr-value).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "aria-valid-attr": { + "id": "aria-valid-attr", + "title": "`[aria-*]` attributes are valid and not misspelled", + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more about valid ARIA attributes](https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "button-name": { + "id": "button-name", + "title": "Buttons have an accessible name", + "description": "When a button doesn't have an accessible name, screen readers announce it as \"button\", making it unusable for users who rely on screen readers. [Learn how to make buttons more accessible](https://dequeuniversity.com/rules/axe/4.10/button-name).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "bypass": { + "id": "bypass", + "title": "The page contains a heading, skip link, or landmark region", + "description": "Adding ways to bypass repetitive content lets keyboard users navigate the page more efficiently. [Learn more about bypass blocks](https://dequeuniversity.com/rules/axe/4.10/bypass).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "color-contrast": { + "id": "color-contrast", + "title": "Background and foreground colors have a sufficient contrast ratio", + "description": "Low-contrast text is difficult or impossible for many users to read. [Learn how to provide sufficient color contrast](https://dequeuniversity.com/rules/axe/4.10/color-contrast).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "relatedNode", + "valueType": "node" + }, + "label": "Failing Elements" + } + ], + "items": [] + } + }, + "definition-list": { + "id": "definition-list", + "title": "`
        `'s contain only properly-ordered `
        ` and `
        ` groups, `", + "message": "Syntax not understood" + }, + { + "index": "26", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "27", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "28", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "29", + "line": "
        ", + "message": "Syntax not understood" + }, + { + "index": "30", + "line": "
        ", + "message": "Syntax not understood" + }, + { + "index": "31", + "line": "
        ", + "message": "Syntax not understood" + }, + { + "index": "32", + "line": "
        ", + "message": "Syntax not understood" + }, + { + "index": "33", + "line": "
        ", + "message": "Syntax not understood" + }, + { + "index": "34", + "line": "
        ", + "message": "Syntax not understood" + }, + { + "index": "35", + "line": "
        ", + "message": "Syntax not understood" + }, + { + "index": "36", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "37", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "40", + "line": " ", + "message": "Unknown directive" + }, + { + "index": "41", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "47", + "line": " ", + "message": "Syntax not understood" + }, + { + "index": "48", + "line": "", + "message": "Syntax not understood" + } + ] + } + }, + "hreflang": { + "id": "hreflang", + "title": "Document has a valid `hreflang`", + "description": "hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more about `hreflang`](https://developer.chrome.com/docs/lighthouse/seo/hreflang/).", + "score": 1, + "scoreDisplayMode": "binary", + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + }, + "label": "" + } + ], + "items": [] + } + }, + "canonical": { + "id": "canonical", + "title": "Document has a valid `rel=canonical`", + "description": "Canonical links suggest which URL to show in search results. [Learn more about canonical links](https://developer.chrome.com/docs/lighthouse/seo/canonical/).", + "score": null, + "scoreDisplayMode": "notApplicable" + }, + "structured-data": { + "id": "structured-data", + "title": "Structured data is valid", + "description": "Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more about Structured Data](https://developer.chrome.com/docs/lighthouse/seo/structured-data/).", + "score": null, + "scoreDisplayMode": "manual" + }, + "bf-cache": { + "id": "bf-cache", + "title": "Page didn't prevent back/forward cache restoration", + "description": "Many navigations are performed by going back to a previous page, or forwards again. The back/forward cache (bfcache) can speed up these return navigations. [Learn more about the bfcache](https://developer.chrome.com/docs/lighthouse/performance/bf-cache/)", + "score": 1, + "scoreDisplayMode": "binary", + "guidanceLevel": 4 + }, + "cache-insight": { + "id": "cache-insight", + "title": "Use efficient cache lifetimes", + "description": "A long cache lifetime can speed up repeat visits to your page. [Learn more](https://web.dev/uses-long-cache-ttl/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "Request" + }, + { + "key": "cacheLifetimeMs", + "valueType": "ms", + "label": "Cache TTL", + "displayUnit": "duration" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size", + "displayUnit": "kb", + "granularity": 1 + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["uses-long-cache-ttl"] + }, + "cls-culprits-insight": { + "id": "cls-culprits-insight", + "title": "Layout shift culprits", + "description": "Layout shifts occur when elements move absent any user interaction. [Investigate the causes of layout shifts](https://web.dev/articles/optimize-cls), such as elements being added, removed, or their fonts changing as the page loads.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "CLS": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "subItemsHeading": { + "key": "extra" + }, + "label": "Element" + }, + { + "key": "score", + "valueType": "numeric", + "subItemsHeading": { + "key": "cause", + "valueType": "text" + }, + "granularity": 0.001, + "label": "Layout shift score" + } + ], + "items": [ + { + "node": { + "type": "text", + "value": "Total" + }, + "score": 0.176479 + }, + { + "node": { + "type": "node", + "lhId": "page-1-DIV", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,1,DIV", + "selector": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual", + "boundingRect": { + "top": 494, + "bottom": 674, + "left": 24, + "right": 388, + "width": 364, + "height": 180 + }, + "snippet": "
        ", + "nodeLabel": "section#sobre-o-projeto > div.container > div.home-hero__grid > div.home-hero__visual" + }, + "score": 0.176479 + } + ] + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": [ + "layout-shifts", + "non-composited-animations", + "unsized-images" + ] + }, + "document-latency-insight": { + "id": "document-latency-insight", + "title": "Document request latency", + "description": "Your first network request is the most important. Reduce its latency by avoiding redirects, ensuring a fast server response, and enabling text compression.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "checklist", + "items": { + "noRedirects": { + "label": "Avoids redirects", + "value": true + }, + "serverResponseIsFast": { + "label": "Server responds quickly (observed 2 ms)", + "value": true + }, + "usesCompression": { + "label": "Applies text compression", + "value": true + } + }, + "debugData": { + "type": "debugdata", + "redirectDuration": 0, + "serverResponseTime": 2, + "uncompressedResponseBytes": 0, + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "redirects", + "server-response-time", + "uses-text-compression" + ] + }, + "dom-size-insight": { + "id": "dom-size-insight", + "title": "Optimize DOM size", + "description": "A large DOM can increase the duration of style calculations and layout reflows, impacting page responsiveness. A large DOM will also increase memory usage. [Learn how to avoid an excessive DOM size](https://developer.chrome.com/docs/lighthouse/performance/dom-size/).", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "statistic", + "valueType": "text", + "label": "Statistic" + }, + { + "key": "node", + "valueType": "node", + "label": "Element" + }, + { + "key": "value", + "valueType": "numeric", + "label": "Value" + } + ], + "items": [ + { + "statistic": "Total elements", + "value": { + "type": "numeric", + "granularity": 1, + "value": 104 + } + }, + { + "statistic": "Most children", + "node": { + "type": "node", + "lhId": "page-6-UL", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,3,FOOTER,0,DIV,1,NAV,0,DIV,1,UL", + "selector": "div.footer__top > nav.footer__links > div.footer__col > ul", + "boundingRect": { + "top": 1383, + "bottom": 1559, + "left": 24, + "right": 190, + "width": 166, + "height": 176 + }, + "snippet": "
          ", + "nodeLabel": "Música\nModa\nCinema\nLiteratura\nCultura Digital" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 5 + } + }, + { + "statistic": "DOM depth", + "node": { + "type": "node", + "lhId": "page-7-EM", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1,1,EM", + "selector": "div.home-hero__grid > div.home-hero__text > h1#hero-manifesto > em", + "boundingRect": { + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "snippet": "", + "nodeLabel": "Interpop" + }, + "value": { + "type": "numeric", + "granularity": 1, + "value": 10 + } + } + ], + "debugData": { + "type": "debugdata", + "totalElements": 104, + "maxChildren": 5, + "maxDepth": 10 + } + }, + "guidanceLevel": 3, + "replacesAudits": ["dom-size"] + }, + "duplicated-javascript-insight": { + "id": "duplicated-javascript-insight", + "title": "Duplicated JavaScript", + "description": "Remove large, duplicate JavaScript modules from bundles to reduce unnecessary bytes consumed by network activity.", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "code", + "subItemsHeading": { + "key": "url", + "valueType": "url" + }, + "label": "Source" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "subItemsHeading": { + "key": "sourceTransferBytes" + }, + "granularity": 10, + "label": "Duplicated bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2, + "replacesAudits": ["duplicated-javascript"] + }, + "font-display-insight": { + "id": "font-display-insight", + "title": "Font display", + "description": "Consider setting [font-display](https://developer.chrome.com/blog/font-display) to swap or optional to ensure text is consistently visible. swap can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "wastedMs", + "valueType": "ms", + "label": "Est Savings" + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["font-display"] + }, + "forced-reflow-insight": { + "id": "forced-reflow-insight", + "title": "Forced reflow", + "description": "A forced reflow occurs when JavaScript queries geometric properties (such as offsetWidth) after styles have been invalidated by a change to the DOM state. This can result in poor performance. Learn more about [forced reflows](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts) and possible mitigations.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "source", + "valueType": "source-location", + "label": "Source" + }, + { + "key": "reflowTime", + "valueType": "ms", + "granularity": 1, + "label": "Total reflow time" + } + ], + "items": [] + } + ] + }, + "guidanceLevel": 3 + }, + "image-delivery-insight": { + "id": "image-delivery-insight", + "title": "Improve image delivery", + "description": "Reducing the download time of images can improve the perceived load time of the page and LCP. [Learn more about optimizing image size](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL", + "subItemsHeading": { + "key": "reason", + "valueType": "text" + } + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Resource Size" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Est Savings", + "subItemsHeading": { + "key": "wastedBytes", + "valueType": "bytes" + } + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 3, + "replacesAudits": [ + "modern-image-formats", + "uses-optimized-images", + "efficient-animated-content", + "uses-responsive-images" + ] + }, + "inp-breakdown-insight": { + "id": "inp-breakdown-insight", + "title": "INP breakdown", + "description": "Start investigating with the longest subpart. [Delays can be minimized](https://web.dev/articles/optimize-inp#optimize_interactions). To reduce processing duration, [optimize the main-thread costs](https://web.dev/articles/optimize-long-tasks), often JS.", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["work-during-interaction"] + }, + "lcp-breakdown-insight": { + "id": "lcp-breakdown-insight", + "title": "LCP breakdown", + "description": "Each [subpart has specific improvement strategies](https://web.dev/articles/optimize-lcp#lcp-breakdown). Ideally, most of the LCP time should be spent on loading the resources, not within delays.", + "score": 1, + "scoreDisplayMode": "informative", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "table", + "headings": [ + { + "key": "label", + "valueType": "text", + "label": "Subpart" + }, + { + "key": "duration", + "valueType": "ms", + "label": "Duration" + } + ], + "items": [ + { + "subpart": "timeToFirstByte", + "label": "Time to first byte", + "duration": 6.561 + }, + { + "subpart": "elementRenderDelay", + "label": "Element render delay", + "duration": 283.098 + } + ] + }, + { + "type": "node", + "lhId": "page-0-H1", + "path": "1,HTML,1,BODY,1,DIV,0,DIV,2,MAIN,0,SECTION,0,DIV,0,DIV,0,DIV,1,H1", + "selector": "div.container > div.home-hero__grid > div.home-hero__text > h1#hero-manifesto", + "boundingRect": { + "top": 136, + "bottom": 341, + "left": 24, + "right": 388, + "width": 364, + "height": 205 + }, + "snippet": "

          ", + "nodeLabel": "O Interpop é um projeto independente que busca analisar criticamente o Soft Pow…" + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["largest-contentful-paint-element"] + }, + "lcp-discovery-insight": { + "id": "lcp-discovery-insight", + "title": "LCP request discovery", + "description": "Optimize LCP by making the LCP image [discoverable](https://web.dev/articles/optimize-lcp#1_eliminate_resource_load_delay) from the HTML immediately, and [avoiding lazy-loading](https://web.dev/articles/lcp-lazy-loading)", + "score": null, + "scoreDisplayMode": "notApplicable", + "guidanceLevel": 3, + "replacesAudits": ["prioritize-lcp-image", "lcp-lazy-loaded"] + }, + "legacy-javascript-insight": { + "id": "legacy-javascript-insight", + "title": "Legacy JavaScript", + "description": "Polyfills and transforms enable older browsers to use new JavaScript features. However, many aren't necessary for modern browsers. Consider modifying your JavaScript build process to not transpile [Baseline](https://web.dev/articles/baseline-and-polyfills) features, unless you know you must support older browsers. [Learn why most sites can deploy ES6+ code without transpiling](https://philipwalton.com/articles/the-state-of-es5-on-the-web/)", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "subItemsHeading": { + "key": "location", + "valueType": "source-location" + }, + "label": "URL" + }, + { + "key": null, + "valueType": "code", + "subItemsHeading": { + "key": "signal" + }, + "label": "" + }, + { + "key": "wastedBytes", + "valueType": "bytes", + "label": "Wasted bytes" + } + ], + "items": [], + "debugData": { + "type": "debugdata", + "wastedBytes": 0 + } + }, + "guidanceLevel": 2 + }, + "modern-http-insight": { + "id": "modern-http-insight", + "title": "Modern HTTP", + "description": "HTTP/2 and HTTP/3 offer many benefits over HTTP/1.1, such as multiplexing. [Learn more about using modern HTTP](https://developer.chrome.com/docs/lighthouse/best-practices/uses-http2/).", + "score": 1, + "scoreDisplayMode": "metricSavings", + "metricSavings": { + "FCP": 0, + "LCP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "protocol", + "valueType": "text", + "label": "Protocol" + } + ], + "items": [] + }, + "guidanceLevel": 3 + }, + "network-dependency-tree-insight": { + "id": "network-dependency-tree-insight", + "title": "Network dependency tree", + "description": "[Avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains) by reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.", + "score": 0, + "scoreDisplayMode": "numeric", + "metricSavings": { + "LCP": 0 + }, + "details": { + "type": "list", + "items": [ + { + "type": "list-section", + "value": { + "type": "network-tree", + "chains": { + "EDBC1951D08BC8455EFB6D2E114058A0": { + "url": "http://localhost:4173/", + "navStartToEndTime": 23, + "transferSize": 1284, + "isLongest": true, + "children": { + "187378.2": { + "url": "http://localhost:4173/assets/index-BXwNXqtB.js", + "navStartToEndTime": 64, + "transferSize": 116210, + "isLongest": true, + "children": { + "187378.37": { + "url": "http://localhost:8000/api/v1/auth/me/", + "navStartToEndTime": 309, + "transferSize": 0, + "isLongest": true, + "children": {} + }, + "187378.36": { + "url": "http://localhost:8000/api/v1/articles/?status=published&page=1", + "navStartToEndTime": 308, + "transferSize": 0, + "children": {} + } + } + }, + "187378.3": { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "navStartToEndTime": 39, + "transferSize": 11584, + "children": { + "187378.15": { + "url": "http://localhost:4173/assets/inter-latin-wght-normal-Dx4kXJAl.woff2", + "navStartToEndTime": 276, + "transferSize": 48576, + "children": {} + }, + "187378.26": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2", + "navStartToEndTime": 275, + "transferSize": 64840, + "children": {} + }, + "187378.18": { + "url": "http://localhost:4173/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2", + "navStartToEndTime": 274, + "transferSize": 58404, + "children": {} + }, + "187378.23": { + "url": "http://localhost:4173/assets/montserrat-latin-wght-normal-l_AIctKy.woff2", + "navStartToEndTime": 274, + "transferSize": 38276, + "children": {} + } + } + }, + "187378.8": { + "url": "http://localhost:4173/site.webmanifest", + "navStartToEndTime": 43, + "transferSize": 796, + "children": {} + } + } + } + }, + "longestChain": { + "duration": 309 + } + } + }, + { + "type": "list-section", + "title": "Preconnected origins", + "description": "[preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints help the browser establish a connection earlier in the page load, saving time when the first request for that origin is made. The following are the origins that the page preconnected to.", + "value": { + "type": "text", + "value": "no origins were preconnected" + } + }, + { + "type": "list-section", + "title": "Preconnect candidates", + "description": "Add [preconnect](https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/) hints to your most important origins, but try to use no more than 4.", + "value": { + "type": "text", + "value": "No additional origins are good candidates for preconnecting" + } + } + ] + }, + "guidanceLevel": 1, + "replacesAudits": ["critical-request-chains", "uses-rel-preconnect"] + }, + "render-blocking-insight": { + "id": "render-blocking-insight", + "title": "Render blocking requests", + "description": "Requests are blocking the page's initial render, which may delay LCP. [Deferring or inlining](https://web.dev/learn/performance/understanding-the-critical-path#render-blocking_resources) can move these network requests out of the critical path.", + "score": 0, + "scoreDisplayMode": "metricSavings", + "displayValue": "Est savings of 1,350 ms", + "metricSavings": { + "FCP": 1350, + "LCP": 1350 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "url", + "valueType": "url", + "label": "URL" + }, + { + "key": "totalBytes", + "valueType": "bytes", + "label": "Transfer Size" + }, + { + "key": "wastedMs", + "valueType": "timespanMs", + "label": "Duration" + } + ], + "items": [ + { + "url": "http://localhost:4173/assets/index-DAWkbRUl.css", + "totalBytes": 11584, + "wastedMs": 153 + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["render-blocking-resources"] + }, + "third-parties-insight": { + "id": "third-parties-insight", + "title": "3rd parties", + "description": "3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://web.dev/articles/optimizing-content-efficiency-loading-third-party-javascript/) to prioritize your page's content.", + "score": 1, + "scoreDisplayMode": "numeric", + "details": { + "type": "table", + "headings": [ + { + "key": "entity", + "valueType": "text", + "label": "3rd party", + "subItemsHeading": { + "key": "url", + "valueType": "url" + } + }, + { + "key": "transferSize", + "granularity": 1, + "valueType": "bytes", + "label": "Transfer size", + "subItemsHeading": { + "key": "transferSize" + } + }, + { + "key": "mainThreadTime", + "granularity": 1, + "valueType": "ms", + "label": "Main thread time", + "subItemsHeading": { + "key": "mainThreadTime" + } + } + ], + "items": [] + }, + "guidanceLevel": 3, + "replacesAudits": ["third-party-summary"] + }, + "viewport-insight": { + "id": "viewport-insight", + "title": "Optimize viewport for mobile", + "description": "Tap interactions may be [delayed by up to 300 ms](https://developer.chrome.com/blog/300ms-tap-delay-gone-away/) if the viewport is not optimized for mobile.", + "score": 1, + "scoreDisplayMode": "numeric", + "metricSavings": { + "INP": 0 + }, + "details": { + "type": "table", + "headings": [ + { + "key": "node", + "valueType": "node", + "label": "" + } + ], + "items": [ + { + "node": { + "type": "node", + "lhId": "page-5-META", + "path": "1,HTML,0,HEAD,5,META", + "selector": "head > meta", + "boundingRect": { + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "snippet": "", + "nodeLabel": "head > meta" + } + } + ] + }, + "guidanceLevel": 3, + "replacesAudits": ["viewport"] + } + }, + "configSettings": { + "output": ["json"], + "maxWaitForFcp": 30000, + "maxWaitForLoad": 45000, + "pauseAfterFcpMs": 1000, + "pauseAfterLoadMs": 1000, + "networkQuietThresholdMs": 1000, + "cpuQuietThresholdMs": 1000, + "formFactor": "mobile", + "throttling": { + "rttMs": 150, + "throughputKbps": 1638.4, + "requestLatencyMs": 562.5, + "downloadThroughputKbps": 1474.5600000000002, + "uploadThroughputKbps": 675, + "cpuSlowdownMultiplier": 4 + }, + "throttlingMethod": "simulate", + "screenEmulation": { + "mobile": true, + "width": 412, + "height": 823, + "deviceScaleFactor": 1.75, + "disabled": false + }, + "emulatedUserAgent": "Mozilla/5.0 (Linux; Android 11; moto g power (2022)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36", + "auditMode": false, + "gatherMode": false, + "clearStorageTypes": [ + "file_systems", + "shader_cache", + "service_workers", + "cache_storage" + ], + "disableStorageReset": false, + "debugNavigation": false, + "channel": "cli", + "usePassiveGathering": false, + "disableFullPageScreenshot": false, + "skipAboutBlank": false, + "blankPage": "about:blank", + "ignoreStatusCode": false, + "locale": "en-US", + "blockedUrlPatterns": null, + "additionalTraceCategories": null, + "extraHeaders": null, + "precomputedLanternData": null, + "onlyAudits": null, + "onlyCategories": null, + "skipAudits": null + }, + "categories": { + "performance": { + "title": "Performance", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "first-contentful-paint", + "weight": 10, + "group": "metrics", + "acronym": "FCP" + }, + { + "id": "largest-contentful-paint", + "weight": 25, + "group": "metrics", + "acronym": "LCP" + }, + { + "id": "total-blocking-time", + "weight": 30, + "group": "metrics", + "acronym": "TBT" + }, + { + "id": "cumulative-layout-shift", + "weight": 25, + "group": "metrics", + "acronym": "CLS" + }, + { + "id": "speed-index", + "weight": 10, + "group": "metrics", + "acronym": "SI" + }, + { + "id": "cache-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "cls-culprits-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "document-latency-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "dom-size-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "duplicated-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "font-display-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "forced-reflow-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "image-delivery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "inp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-breakdown-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "lcp-discovery-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "legacy-javascript-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "modern-http-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-dependency-tree-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "render-blocking-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "third-parties-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "viewport-insight", + "weight": 0, + "group": "hidden" + }, + { + "id": "interactive", + "weight": 0, + "group": "hidden", + "acronym": "TTI" + }, + { + "id": "max-potential-fid", + "weight": 0, + "group": "hidden" + }, + { + "id": "first-meaningful-paint", + "weight": 0, + "acronym": "FMP", + "group": "hidden" + }, + { + "id": "render-blocking-resources", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-responsive-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "offscreen-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-css", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unminified-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-css-rules", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unused-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-optimized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "modern-image-formats", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-text-compression", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-rel-preconnect", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "server-response-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "redirects", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-http2", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "efficient-animated-content", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "duplicated-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "legacy-javascript", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "prioritize-lcp-image", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "total-byte-weight", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-long-cache-ttl", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "dom-size", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "critical-request-chains", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "user-timings", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bootup-time", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "mainthread-work-breakdown", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "font-display", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-summary", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "third-party-facades", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "largest-contentful-paint-element", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "lcp-lazy-loaded", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "layout-shifts", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "uses-passive-event-listeners", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "no-document-write", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "long-tasks", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "non-composited-animations", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "unsized-images", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "viewport", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "bf-cache", + "weight": 0, + "group": "diagnostics" + }, + { + "id": "network-requests", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-rtt", + "weight": 0, + "group": "hidden" + }, + { + "id": "network-server-latency", + "weight": 0, + "group": "hidden" + }, + { + "id": "main-thread-tasks", + "weight": 0, + "group": "hidden" + }, + { + "id": "diagnostics", + "weight": 0, + "group": "hidden" + }, + { + "id": "metrics", + "weight": 0, + "group": "hidden" + }, + { + "id": "screenshot-thumbnails", + "weight": 0, + "group": "hidden" + }, + { + "id": "final-screenshot", + "weight": 0, + "group": "hidden" + }, + { + "id": "script-treemap-data", + "weight": 0, + "group": "hidden" + }, + { + "id": "resource-summary", + "weight": 0, + "group": "hidden" + } + ], + "id": "performance", + "score": 0.81 + }, + "accessibility": { + "title": "Accessibility", + "description": "These checks highlight opportunities to [improve the accessibility of your web app](https://developer.chrome.com/docs/lighthouse/accessibility/). Automatic detection can only detect a subset of issues and does not guarantee the accessibility of your web app, so [manual testing](https://web.dev/articles/how-to-review) is also encouraged.", + "manualDescription": "These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://web.dev/articles/how-to-review).", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "accesskeys", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "aria-allowed-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-allowed-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-command-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-conditional-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-deprecated-role", + "weight": 1, + "group": "a11y-aria" + }, + { + "id": "aria-dialog-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-body", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-hidden-focus", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-input-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-meter-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-progressbar-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-prohibited-attr", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-required-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-required-children", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-required-parent", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-roles", + "weight": 7, + "group": "a11y-aria" + }, + { + "id": "aria-text", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-toggle-field-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-tooltip-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-treeitem-name", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr-value", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "aria-valid-attr", + "weight": 10, + "group": "a11y-aria" + }, + { + "id": "button-name", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "bypass", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "color-contrast", + "weight": 7, + "group": "a11y-color-contrast" + }, + { + "id": "definition-list", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "dlitem", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "document-title", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "duplicate-id-aria", + "weight": 0, + "group": "a11y-aria" + }, + { + "id": "form-field-multiple-labels", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "frame-title", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "heading-order", + "weight": 3, + "group": "a11y-navigation" + }, + { + "id": "html-has-lang", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-lang-valid", + "weight": 7, + "group": "a11y-language" + }, + { + "id": "html-xml-lang-mismatch", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "image-alt", + "weight": 10, + "group": "a11y-names-labels" + }, + { + "id": "image-redundant-alt", + "weight": 1, + "group": "a11y-names-labels" + }, + { + "id": "input-button-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "input-image-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "label", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "link-in-text-block", + "weight": 0, + "group": "a11y-color-contrast" + }, + { + "id": "link-name", + "weight": 7, + "group": "a11y-names-labels" + }, + { + "id": "list", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "listitem", + "weight": 7, + "group": "a11y-tables-lists" + }, + { + "id": "meta-refresh", + "weight": 0, + "group": "a11y-best-practices" + }, + { + "id": "meta-viewport", + "weight": 10, + "group": "a11y-best-practices" + }, + { + "id": "object-alt", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "select-name", + "weight": 0, + "group": "a11y-names-labels" + }, + { + "id": "skip-link", + "weight": 3, + "group": "a11y-names-labels" + }, + { + "id": "tabindex", + "weight": 0, + "group": "a11y-navigation" + }, + { + "id": "table-duplicate-name", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "target-size", + "weight": 7, + "group": "a11y-best-practices" + }, + { + "id": "td-headers-attr", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "th-has-data-cells", + "weight": 0, + "group": "a11y-tables-lists" + }, + { + "id": "valid-lang", + "weight": 0, + "group": "a11y-language" + }, + { + "id": "video-caption", + "weight": 0, + "group": "a11y-audio-video" + }, + { + "id": "focusable-controls", + "weight": 0 + }, + { + "id": "interactive-element-affordance", + "weight": 0 + }, + { + "id": "logical-tab-order", + "weight": 0 + }, + { + "id": "visual-order-follows-dom", + "weight": 0 + }, + { + "id": "focus-traps", + "weight": 0 + }, + { + "id": "managed-focus", + "weight": 0 + }, + { + "id": "use-landmarks", + "weight": 0 + }, + { + "id": "offscreen-content-hidden", + "weight": 0 + }, + { + "id": "custom-controls-labels", + "weight": 0 + }, + { + "id": "custom-controls-roles", + "weight": 0 + }, + { + "id": "empty-heading", + "weight": 0, + "group": "hidden" + }, + { + "id": "identical-links-same-purpose", + "weight": 0, + "group": "hidden" + }, + { + "id": "landmark-one-main", + "weight": 0, + "group": "hidden" + }, + { + "id": "label-content-name-mismatch", + "weight": 0, + "group": "hidden" + }, + { + "id": "table-fake-caption", + "weight": 0, + "group": "hidden" + }, + { + "id": "td-has-header", + "weight": 0, + "group": "hidden" + } + ], + "id": "accessibility", + "score": 1 + }, + "best-practices": { + "title": "Best Practices", + "supportedModes": ["navigation", "timespan", "snapshot"], + "auditRefs": [ + { + "id": "is-on-https", + "weight": 5, + "group": "best-practices-trust-safety" + }, + { + "id": "redirects-http", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "geolocation-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "notification-on-start", + "weight": 1, + "group": "best-practices-trust-safety" + }, + { + "id": "csp-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "has-hsts", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "origin-isolation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "clickjacking-mitigation", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "trusted-types-xss", + "weight": 0, + "group": "best-practices-trust-safety" + }, + { + "id": "paste-preventing-inputs", + "weight": 3, + "group": "best-practices-ux" + }, + { + "id": "image-aspect-ratio", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "image-size-responsive", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "viewport", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "font-size", + "weight": 1, + "group": "best-practices-ux" + }, + { + "id": "doctype", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "charset", + "weight": 1, + "group": "best-practices-browser-compat" + }, + { + "id": "js-libraries", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "deprecations", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "third-party-cookies", + "weight": 5, + "group": "best-practices-general" + }, + { + "id": "errors-in-console", + "weight": 1, + "group": "best-practices-general" + }, + { + "id": "valid-source-maps", + "weight": 0, + "group": "best-practices-general" + }, + { + "id": "inspector-issues", + "weight": 1, + "group": "best-practices-general" + } + ], + "id": "best-practices", + "score": 0.96 + }, + "seo": { + "title": "SEO", + "description": "These checks ensure that your page is following basic search engine optimization advice. There are many additional factors Lighthouse does not score here that may affect your search ranking, including performance on [Core Web Vitals](https://web.dev/explore/vitals). [Learn more about Google Search Essentials](https://support.google.com/webmasters/answer/35769).", + "manualDescription": "Run these additional validators on your site to check additional SEO best practices.", + "supportedModes": ["navigation", "snapshot"], + "auditRefs": [ + { + "id": "is-crawlable", + "weight": 4.043478260869565, + "group": "seo-crawl" + }, + { + "id": "document-title", + "weight": 1, + "group": "seo-content" + }, + { + "id": "meta-description", + "weight": 1, + "group": "seo-content" + }, + { + "id": "http-status-code", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "link-text", + "weight": 1, + "group": "seo-content" + }, + { + "id": "crawlable-anchors", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "robots-txt", + "weight": 1, + "group": "seo-crawl" + }, + { + "id": "image-alt", + "weight": 1, + "group": "seo-content" + }, + { + "id": "hreflang", + "weight": 1, + "group": "seo-content" + }, + { + "id": "canonical", + "weight": 0, + "group": "seo-content" + }, + { + "id": "structured-data", + "weight": 0 + } + ], + "id": "seo", + "score": 0.92 + } + }, + "categoryGroups": { + "metrics": { + "title": "Metrics" + }, + "insights": { + "title": "Insights", + "description": "These insights are also available in the Chrome DevTools Performance Panel - [record a trace](https://developer.chrome.com/docs/devtools/performance/reference) to view more detailed information." + }, + "diagnostics": { + "title": "Diagnostics", + "description": "More information about the performance of your application. These numbers don't [directly affect](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) the Performance score." + }, + "a11y-best-practices": { + "title": "Best practices", + "description": "These items highlight common accessibility best practices." + }, + "a11y-color-contrast": { + "title": "Contrast", + "description": "These are opportunities to improve the legibility of your content." + }, + "a11y-names-labels": { + "title": "Names and labels", + "description": "These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-navigation": { + "title": "Navigation", + "description": "These are opportunities to improve keyboard navigation in your application." + }, + "a11y-aria": { + "title": "ARIA", + "description": "These are opportunities to improve the usage of ARIA in your application which may enhance the experience for users of assistive technology, like a screen reader." + }, + "a11y-language": { + "title": "Internationalization and localization", + "description": "These are opportunities to improve the interpretation of your content by users in different locales." + }, + "a11y-audio-video": { + "title": "Audio and video", + "description": "These are opportunities to provide alternative content for audio and video. This may improve the experience for users with hearing or vision impairments." + }, + "a11y-tables-lists": { + "title": "Tables and lists", + "description": "These are opportunities to improve the experience of reading tabular or list data using assistive technology, like a screen reader." + }, + "seo-mobile": { + "title": "Mobile Friendly", + "description": "Make sure your pages are mobile friendly so users don’t have to pinch or zoom in order to read the content pages. [Learn how to make pages mobile-friendly](https://developers.google.com/search/mobile-sites/)." + }, + "seo-content": { + "title": "Content Best Practices", + "description": "Format your HTML in a way that enables crawlers to better understand your app’s content." + }, + "seo-crawl": { + "title": "Crawling and Indexing", + "description": "To appear in search results, crawlers need access to your app." + }, + "best-practices-trust-safety": { + "title": "Trust and Safety" + }, + "best-practices-ux": { + "title": "User Experience" + }, + "best-practices-browser-compat": { + "title": "Browser Compatibility" + }, + "best-practices-general": { + "title": "General" + }, + "hidden": { + "title": "" + } + }, + "stackPacks": [], + "entities": [ + { + "name": "localhost", + "origins": ["http://localhost:4173", "http://localhost:8000"], + "isFirstParty": true, + "isUnrecognized": true + }, + { + "name": "vlibras.gov.br", + "origins": ["https://vlibras.gov.br"], + "isUnrecognized": true + } + ], + "fullPageScreenshot": { + "screenshot": { + "data": "data:image/webp;base64,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", + "width": 780, + "height": 1951 + }, + "nodes": { + "page-0-H1": { + "id": "hero-manifesto", + "top": 136, + "bottom": 239, + "left": 24, + "right": 595, + "width": 571, + "height": 102 + }, + "page-1-DIV": { + "id": "", + "top": 364, + "bottom": 544, + "left": 24, + "right": 756, + "width": 732, + "height": 180 + }, + "page-2-SECTION": { + "id": "", + "top": 569, + "bottom": 941, + "left": 0, + "right": 780, + "width": 780, + "height": 372 + }, + "page-3-P": { + "id": "", + "top": 259, + "bottom": 340, + "left": 24, + "right": 549, + "width": 525, + "height": 82 + }, + "page-4-EM": { + "id": "", + "top": 173, + "bottom": 201, + "left": 287, + "right": 404, + "width": 117, + "height": 28 + }, + "page-5-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "page-6-UL": { + "id": "", + "top": 1638, + "bottom": 1814, + "left": 24, + "right": 247, + "width": 223, + "height": 176 + }, + "page-7-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "page-8-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 200, + "right": 580, + "width": 380, + "height": 111 + }, + "page-9-IMG": { + "id": "", + "top": 1887, + "bottom": 1927, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "page-10-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-0-A": { + "id": "", + "top": -81, + "bottom": -34, + "left": 12, + "right": 194, + "width": 182, + "height": 46 + }, + "1-1-A": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-2-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-3-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-4-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-5-A": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-6-A": { + "id": "", + "top": 586, + "bottom": 609, + "left": 666, + "right": 756, + "width": 90, + "height": 22 + }, + "1-7-A": { + "id": "", + "top": 829, + "bottom": 877, + "left": 276, + "right": 504, + "width": 229, + "height": 48 + }, + "1-8-A": { + "id": "", + "top": 1642, + "bottom": 1659, + "left": 24, + "right": 72, + "width": 48, + "height": 17 + }, + "1-9-A": { + "id": "", + "top": 1680, + "bottom": 1697, + "left": 24, + "right": 61, + "width": 37, + "height": 17 + }, + "1-10-A": { + "id": "", + "top": 1717, + "bottom": 1734, + "left": 24, + "right": 74, + "width": 50, + "height": 17 + }, + "1-11-A": { + "id": "", + "top": 1755, + "bottom": 1772, + "left": 24, + "right": 87, + "width": 63, + "height": 17 + }, + "1-12-A": { + "id": "", + "top": 1792, + "bottom": 1809, + "left": 24, + "right": 117, + "width": 93, + "height": 17 + }, + "1-13-A": { + "id": "", + "top": 1642, + "bottom": 1659, + "left": 279, + "right": 346, + "width": 67, + "height": 17 + }, + "1-14-A": { + "id": "", + "top": 1680, + "bottom": 1697, + "left": 279, + "right": 336, + "width": 58, + "height": 17 + }, + "1-15-A": { + "id": "", + "top": 1717, + "bottom": 1734, + "left": 279, + "right": 333, + "width": 54, + "height": 17 + }, + "1-16-A": { + "id": "", + "top": 1755, + "bottom": 1772, + "left": 279, + "right": 331, + "width": 52, + "height": 17 + }, + "1-17-A": { + "id": "", + "top": 1642, + "bottom": 1659, + "left": 533, + "right": 573, + "width": 40, + "height": 17 + }, + "1-18-A": { + "id": "", + "top": 1680, + "bottom": 1697, + "left": 533, + "right": 607, + "width": 73, + "height": 17 + }, + "1-19-A": { + "id": "", + "top": 1717, + "bottom": 1734, + "left": 533, + "right": 646, + "width": 112, + "height": 17 + }, + "1-20-A": { + "id": "", + "top": 1887, + "bottom": 1927, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-21-A": { + "id": "", + "top": 1897, + "bottom": 1917, + "left": 393, + "right": 460, + "width": 67, + "height": 19 + }, + "1-22-A": { + "id": "", + "top": 1897, + "bottom": 1917, + "left": 480, + "right": 564, + "width": 84, + "height": 19 + }, + "1-23-EM": { + "id": "", + "top": 139, + "bottom": 167, + "left": 51, + "right": 144, + "width": 93, + "height": 28 + }, + "1-24-UL": { + "id": "", + "top": 1638, + "bottom": 1814, + "left": 24, + "right": 247, + "width": 223, + "height": 176 + }, + "1-25-FORM": { + "id": "", + "top": 1528, + "bottom": 1571, + "left": 24, + "right": 756, + "width": 732, + "height": 43 + }, + "1-26-INPUT": { + "id": "", + "top": 1528, + "bottom": 1571, + "left": 24, + "right": 641, + "width": 617, + "height": 43 + }, + "1-27-IMG": { + "id": "", + "top": 13, + "bottom": 51, + "left": 24, + "right": 154, + "width": 130, + "height": 38 + }, + "1-28-IMG": { + "id": "", + "top": 399, + "bottom": 510, + "left": 200, + "right": 580, + "width": 380, + "height": 111 + }, + "1-29-IMG": { + "id": "", + "top": 1887, + "bottom": 1927, + "left": 24, + "right": 161, + "width": 137, + "height": 40 + }, + "1-30-IMG": { + "id": "", + "top": 1898, + "bottom": 1916, + "left": 716, + "right": 756, + "width": 40, + "height": 18 + }, + "1-31-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-32-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-33-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-34-LINK": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-35-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-36-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-37-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-38-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-39-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-40-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-41-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + }, + "1-42-META": { + "id": "", + "top": 0, + "bottom": 0, + "left": 0, + "right": 0, + "width": 0, + "height": 0 + } + } + }, + "timing": { + "entries": [ + { + "startTime": 2255.63, + "name": "lh:config", + "duration": 1079.8, + "entryType": "measure" + }, + { + "startTime": 2258.35, + "name": "lh:config:resolveArtifactsToDefns", + "duration": 67.44, + "entryType": "measure" + }, + { + "startTime": 3335.62, + "name": "lh:runner:gather", + "duration": 7278.01, + "entryType": "measure" + }, + { + "startTime": 3432.44, + "name": "lh:driver:connect", + "duration": 11.2, + "entryType": "measure" + }, + { + "startTime": 3444.01, + "name": "lh:driver:navigate", + "duration": 9.24, + "entryType": "measure" + }, + { + "startTime": 3453.7, + "name": "lh:gather:getBenchmarkIndex", + "duration": 1008.72, + "entryType": "measure" + }, + { + "startTime": 4462.76, + "name": "lh:gather:getVersion", + "duration": 2.11, + "entryType": "measure" + }, + { + "startTime": 4465, + "name": "lh:gather:getDevicePixelRatio", + "duration": 1.39, + "entryType": "measure" + }, + { + "startTime": 4466.87, + "name": "lh:prepare:navigationMode", + "duration": 71.17, + "entryType": "measure" + }, + { + "startTime": 4498.56, + "name": "lh:storage:clearDataForOrigin", + "duration": 30.1, + "entryType": "measure" + }, + { + "startTime": 4528.81, + "name": "lh:storage:clearBrowserCaches", + "duration": 6.19, + "entryType": "measure" + }, + { + "startTime": 4536.28, + "name": "lh:gather:prepareThrottlingAndNetwork", + "duration": 1.72, + "entryType": "measure" + }, + { + "startTime": 4587.92, + "name": "lh:driver:navigate", + "duration": 2673.89, + "entryType": "measure" + }, + { + "startTime": 7575.64, + "name": "lh:computed:NetworkRecords", + "duration": 1.19, + "entryType": "measure" + }, + { + "startTime": 7577.43, + "name": "lh:gather:getArtifact:DevtoolsLog", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 7577.51, + "name": "lh:gather:getArtifact:Trace", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 7577.57, + "name": "lh:gather:getArtifact:Accessibility", + "duration": 429.7, + "entryType": "measure" + }, + { + "startTime": 8007.31, + "name": "lh:gather:getArtifact:AnchorElements", + "duration": 406.46, + "entryType": "measure" + }, + { + "startTime": 8413.82, + "name": "lh:gather:getArtifact:ConsoleMessages", + "duration": 0.07, + "entryType": "measure" + }, + { + "startTime": 8413.91, + "name": "lh:gather:getArtifact:CSSUsage", + "duration": 58.2, + "entryType": "measure" + }, + { + "startTime": 8472.17, + "name": "lh:gather:getArtifact:Doctype", + "duration": 2.13, + "entryType": "measure" + }, + { + "startTime": 8474.34, + "name": "lh:gather:getArtifact:DOMStats", + "duration": 7.2, + "entryType": "measure" + }, + { + "startTime": 8481.57, + "name": "lh:gather:getArtifact:FontSize", + "duration": 24.5, + "entryType": "measure" + }, + { + "startTime": 8506.1, + "name": "lh:gather:getArtifact:Inputs", + "duration": 5.77, + "entryType": "measure" + }, + { + "startTime": 8511.89, + "name": "lh:gather:getArtifact:ImageElements", + "duration": 57.92, + "entryType": "measure" + }, + { + "startTime": 8569.99, + "name": "lh:gather:getArtifact:InspectorIssues", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 8570.26, + "name": "lh:gather:getArtifact:JsUsage", + "duration": 0.06, + "entryType": "measure" + }, + { + "startTime": 8570.35, + "name": "lh:gather:getArtifact:LinkElements", + "duration": 5.46, + "entryType": "measure" + }, + { + "startTime": 8575.43, + "name": "lh:computed:MainResource", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 8575.85, + "name": "lh:gather:getArtifact:MainDocumentContent", + "duration": 1.86, + "entryType": "measure" + }, + { + "startTime": 8577.74, + "name": "lh:gather:getArtifact:MetaElements", + "duration": 4.76, + "entryType": "measure" + }, + { + "startTime": 8582.57, + "name": "lh:gather:getArtifact:NetworkUserAgent", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 8582.77, + "name": "lh:gather:getArtifact:OptimizedImages", + "duration": 0.55, + "entryType": "measure" + }, + { + "startTime": 8583.37, + "name": "lh:gather:getArtifact:ResponseCompression", + "duration": 0.85, + "entryType": "measure" + }, + { + "startTime": 8584.31, + "name": "lh:gather:getArtifact:RobotsTxt", + "duration": 7.58, + "entryType": "measure" + }, + { + "startTime": 8591.92, + "name": "lh:gather:getArtifact:Scripts", + "duration": 0.14, + "entryType": "measure" + }, + { + "startTime": 8592.1, + "name": "lh:gather:getArtifact:SourceMaps", + "duration": 0.07, + "entryType": "measure" + }, + { + "startTime": 8592.19, + "name": "lh:gather:getArtifact:Stacks", + "duration": 12.73, + "entryType": "measure" + }, + { + "startTime": 8592.26, + "name": "lh:gather:collectStacks", + "duration": 12.63, + "entryType": "measure" + }, + { + "startTime": 8604.92, + "name": "lh:gather:getArtifact:Stylesheets", + "duration": 12.51, + "entryType": "measure" + }, + { + "startTime": 8617.52, + "name": "lh:gather:getArtifact:TraceElements", + "duration": 378.43, + "entryType": "measure" + }, + { + "startTime": 8617.92, + "name": "lh:computed:TraceEngineResult", + "duration": 323.36, + "entryType": "measure" + }, + { + "startTime": 8618.06, + "name": "lh:computed:ProcessedTrace", + "duration": 27.12, + "entryType": "measure" + }, + { + "startTime": 8647.34, + "name": "lh:computed:TraceEngineResult:total", + "duration": 282.69, + "entryType": "measure" + }, + { + "startTime": 8647.37, + "name": "lh:computed:TraceEngineResult:parse", + "duration": 218.22, + "entryType": "measure" + }, + { + "startTime": 8648.77, + "name": "lh:computed:TraceEngineResult:parse:handleEvent", + "duration": 142.29, + "entryType": "measure" + }, + { + "startTime": 8791.23, + "name": "lh:computed:TraceEngineResult:parse:Meta:finalize", + "duration": 3.97, + "entryType": "measure" + }, + { + "startTime": 8795.39, + "name": "lh:computed:TraceEngineResult:parse:AnimationFrames:finalize", + "duration": 1.35, + "entryType": "measure" + }, + { + "startTime": 8796.8, + "name": "lh:computed:TraceEngineResult:parse:Animations:finalize", + "duration": 1.9, + "entryType": "measure" + }, + { + "startTime": 8798.74, + "name": "lh:computed:TraceEngineResult:parse:Samples:finalize", + "duration": 1.86, + "entryType": "measure" + }, + { + "startTime": 8800.72, + "name": "lh:computed:TraceEngineResult:parse:AuctionWorklets:finalize", + "duration": 1.43, + "entryType": "measure" + }, + { + "startTime": 8802.41, + "name": "lh:computed:TraceEngineResult:parse:NetworkRequests:finalize", + "duration": 5.42, + "entryType": "measure" + }, + { + "startTime": 8807.93, + "name": "lh:computed:TraceEngineResult:parse:Renderer:finalize", + "duration": 9.78, + "entryType": "measure" + }, + { + "startTime": 8817.76, + "name": "lh:computed:TraceEngineResult:parse:Flows:finalize", + "duration": 4.25, + "entryType": "measure" + }, + { + "startTime": 8822.05, + "name": "lh:computed:TraceEngineResult:parse:AsyncJSCalls:finalize", + "duration": 2.12, + "entryType": "measure" + }, + { + "startTime": 8824.28, + "name": "lh:computed:TraceEngineResult:parse:DOMStats:finalize", + "duration": 1.27, + "entryType": "measure" + }, + { + "startTime": 8825.62, + "name": "lh:computed:TraceEngineResult:parse:UserTimings:finalize", + "duration": 1.26, + "entryType": "measure" + }, + { + "startTime": 8826.93, + "name": "lh:computed:TraceEngineResult:parse:ExtensionTraceData:finalize", + "duration": 1.79, + "entryType": "measure" + }, + { + "startTime": 8828.77, + "name": "lh:computed:TraceEngineResult:parse:LayerTree:finalize", + "duration": 2.29, + "entryType": "measure" + }, + { + "startTime": 8831.15, + "name": "lh:computed:TraceEngineResult:parse:Frames:finalize", + "duration": 6.92, + "entryType": "measure" + }, + { + "startTime": 8838.11, + "name": "lh:computed:TraceEngineResult:parse:GPU:finalize", + "duration": 1.26, + "entryType": "measure" + }, + { + "startTime": 8839.41, + "name": "lh:computed:TraceEngineResult:parse:ImagePainting:finalize", + "duration": 1.35, + "entryType": "measure" + }, + { + "startTime": 8840.8, + "name": "lh:computed:TraceEngineResult:parse:Initiators:finalize", + "duration": 1.7, + "entryType": "measure" + }, + { + "startTime": 8842.55, + "name": "lh:computed:TraceEngineResult:parse:Invalidations:finalize", + "duration": 1.22, + "entryType": "measure" + }, + { + "startTime": 8843.8, + "name": "lh:computed:TraceEngineResult:parse:PageLoadMetrics:finalize", + "duration": 2.86, + "entryType": "measure" + }, + { + "startTime": 8846.7, + "name": "lh:computed:TraceEngineResult:parse:LargestImagePaint:finalize", + "duration": 1.5, + "entryType": "measure" + }, + { + "startTime": 8848.24, + "name": "lh:computed:TraceEngineResult:parse:LargestTextPaint:finalize", + "duration": 1.21, + "entryType": "measure" + }, + { + "startTime": 8849.48, + "name": "lh:computed:TraceEngineResult:parse:Screenshots:finalize", + "duration": 2.15, + "entryType": "measure" + }, + { + "startTime": 8851.68, + "name": "lh:computed:TraceEngineResult:parse:LayoutShifts:finalize", + "duration": 2.36, + "entryType": "measure" + }, + { + "startTime": 8854.07, + "name": "lh:computed:TraceEngineResult:parse:Memory:finalize", + "duration": 1.13, + "entryType": "measure" + }, + { + "startTime": 8855.22, + "name": "lh:computed:TraceEngineResult:parse:PageFrames:finalize", + "duration": 1.23, + "entryType": "measure" + }, + { + "startTime": 8856.5, + "name": "lh:computed:TraceEngineResult:parse:Scripts:finalize", + "duration": 1.87, + "entryType": "measure" + }, + { + "startTime": 8858.4, + "name": "lh:computed:TraceEngineResult:parse:SelectorStats:finalize", + "duration": 1.27, + "entryType": "measure" + }, + { + "startTime": 8859.7, + "name": "lh:computed:TraceEngineResult:parse:UserInteractions:finalize", + "duration": 1.54, + "entryType": "measure" + }, + { + "startTime": 8861.26, + "name": "lh:computed:TraceEngineResult:parse:Workers:finalize", + "duration": 1.17, + "entryType": "measure" + }, + { + "startTime": 8862.46, + "name": "lh:computed:TraceEngineResult:parse:Warnings:finalize", + "duration": 1.7, + "entryType": "measure" + }, + { + "startTime": 8864.19, + "name": "lh:computed:TraceEngineResult:parse:clone", + "duration": 1.37, + "entryType": "measure" + }, + { + "startTime": 8865.59, + "name": "lh:computed:TraceEngineResult:insights", + "duration": 64.42, + "entryType": "measure" + }, + { + "startTime": 8865.94, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 0.55, + "entryType": "measure" + }, + { + "startTime": 8866.51, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 8866.76, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 0.55, + "entryType": "measure" + }, + { + "startTime": 8867.32, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 8867.52, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 1.72, + "entryType": "measure" + }, + { + "startTime": 8869.27, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 8869.5, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 8869.76, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 8869.89, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.35, + "entryType": "measure" + }, + { + "startTime": 8870.27, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 8870.45, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 8870.64, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 8870.87, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 8871.18, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 0.13, + "entryType": "measure" + }, + { + "startTime": 8871.32, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 8871.49, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.28, + "entryType": "measure" + }, + { + "startTime": 8871.79, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 2.12, + "entryType": "measure" + }, + { + "startTime": 8873.99, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.36, + "entryType": "measure" + }, + { + "startTime": 8874.78, + "name": "lh:computed:TraceEngineResult:insights:createLanternContext", + "duration": 28.86, + "entryType": "measure" + }, + { + "startTime": 8903.72, + "name": "lh:computed:TraceEngineResult:insights:CLSCulprits", + "duration": 0.74, + "entryType": "measure" + }, + { + "startTime": 8904.48, + "name": "lh:computed:TraceEngineResult:insights:Cache", + "duration": 0.51, + "entryType": "measure" + }, + { + "startTime": 8905.01, + "name": "lh:computed:TraceEngineResult:insights:DOMSize", + "duration": 0.21, + "entryType": "measure" + }, + { + "startTime": 8905.22, + "name": "lh:computed:TraceEngineResult:insights:DocumentLatency", + "duration": 0.34, + "entryType": "measure" + }, + { + "startTime": 8905.57, + "name": "lh:computed:TraceEngineResult:insights:DuplicatedJavaScript", + "duration": 0.32, + "entryType": "measure" + }, + { + "startTime": 8905.91, + "name": "lh:computed:TraceEngineResult:insights:FontDisplay", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 8906.1, + "name": "lh:computed:TraceEngineResult:insights:ForcedReflow", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 8906.25, + "name": "lh:computed:TraceEngineResult:insights:INPBreakdown", + "duration": 0.06, + "entryType": "measure" + }, + { + "startTime": 8906.32, + "name": "lh:computed:TraceEngineResult:insights:ImageDelivery", + "duration": 0.24, + "entryType": "measure" + }, + { + "startTime": 8906.57, + "name": "lh:computed:TraceEngineResult:insights:LCPBreakdown", + "duration": 0.29, + "entryType": "measure" + }, + { + "startTime": 8906.87, + "name": "lh:computed:TraceEngineResult:insights:LCPDiscovery", + "duration": 0.04, + "entryType": "measure" + }, + { + "startTime": 8906.93, + "name": "lh:computed:TraceEngineResult:insights:LegacyJavaScript", + "duration": 8.88, + "entryType": "measure" + }, + { + "startTime": 8915.83, + "name": "lh:computed:TraceEngineResult:insights:ModernHTTP", + "duration": 2.27, + "entryType": "measure" + }, + { + "startTime": 8918.12, + "name": "lh:computed:TraceEngineResult:insights:NetworkDependencyTree", + "duration": 1.67, + "entryType": "measure" + }, + { + "startTime": 8919.81, + "name": "lh:computed:TraceEngineResult:insights:RenderBlocking", + "duration": 0.84, + "entryType": "measure" + }, + { + "startTime": 8920.67, + "name": "lh:computed:TraceEngineResult:insights:SlowCSSSelector", + "duration": 0.03, + "entryType": "measure" + }, + { + "startTime": 8920.71, + "name": "lh:computed:TraceEngineResult:insights:ThirdParties", + "duration": 8.03, + "entryType": "measure" + }, + { + "startTime": 8928.76, + "name": "lh:computed:TraceEngineResult:insights:Viewport", + "duration": 0.05, + "entryType": "measure" + }, + { + "startTime": 8945.63, + "name": "lh:computed:ProcessedNavigation", + "duration": 2.06, + "entryType": "measure" + }, + { + "startTime": 8947.96, + "name": "lh:computed:CumulativeLayoutShift", + "duration": 0.89, + "entryType": "measure" + }, + { + "startTime": 8949.87, + "name": "lh:computed:Responsiveness", + "duration": 0.17, + "entryType": "measure" + }, + { + "startTime": 8995.99, + "name": "lh:gather:getArtifact:ViewportDimensions", + "duration": 1.86, + "entryType": "measure" + }, + { + "startTime": 8997.88, + "name": "lh:gather:getArtifact:FullPageScreenshot", + "duration": 1264.9, + "entryType": "measure" + }, + { + "startTime": 10262.85, + "name": "lh:gather:getArtifact:BFCacheFailures", + "duration": 287.91, + "entryType": "measure" + }, + { + "startTime": 10614.63, + "name": "lh:runner:audit", + "duration": 1039.18, + "entryType": "measure" + }, + { + "startTime": 10614.84, + "name": "lh:runner:auditing", + "duration": 1037.72, + "entryType": "measure" + }, + { + "startTime": 10616.69, + "name": "lh:audit:is-on-https", + "duration": 3.66, + "entryType": "measure" + }, + { + "startTime": 10620.75, + "name": "lh:audit:redirects-http", + "duration": 1.84, + "entryType": "measure" + }, + { + "startTime": 10623.35, + "name": "lh:audit:viewport", + "duration": 4.46, + "entryType": "measure" + }, + { + "startTime": 10624.66, + "name": "lh:computed:ViewportMeta", + "duration": 1.63, + "entryType": "measure" + }, + { + "startTime": 10628.34, + "name": "lh:audit:first-contentful-paint", + "duration": 13.09, + "entryType": "measure" + }, + { + "startTime": 10629.4, + "name": "lh:computed:FirstContentfulPaint", + "duration": 8.3, + "entryType": "measure" + }, + { + "startTime": 10629.86, + "name": "lh:computed:LanternFirstContentfulPaint", + "duration": 7.82, + "entryType": "measure" + }, + { + "startTime": 10630.14, + "name": "lh:computed:PageDependencyGraph", + "duration": 4.92, + "entryType": "measure" + }, + { + "startTime": 10635.14, + "name": "lh:computed:LoadSimulator", + "duration": 0.76, + "entryType": "measure" + }, + { + "startTime": 10635.24, + "name": "lh:computed:NetworkAnalysis", + "duration": 0.6, + "entryType": "measure" + }, + { + "startTime": 10642.27, + "name": "lh:audit:largest-contentful-paint", + "duration": 8.16, + "entryType": "measure" + }, + { + "startTime": 10644.79, + "name": "lh:computed:LargestContentfulPaint", + "duration": 4.41, + "entryType": "measure" + }, + { + "startTime": 10644.98, + "name": "lh:computed:LanternLargestContentfulPaint", + "duration": 4.2, + "entryType": "measure" + }, + { + "startTime": 10650.77, + "name": "lh:audit:first-meaningful-paint", + "duration": 1.01, + "entryType": "measure" + }, + { + "startTime": 10652.14, + "name": "lh:audit:speed-index", + "duration": 223.71, + "entryType": "measure" + }, + { + "startTime": 10652.6, + "name": "lh:computed:SpeedIndex", + "duration": 220.79, + "entryType": "measure" + }, + { + "startTime": 10652.7, + "name": "lh:computed:LanternSpeedIndex", + "duration": 220.65, + "entryType": "measure" + }, + { + "startTime": 10652.77, + "name": "lh:computed:Speedline", + "duration": 216.21, + "entryType": "measure" + }, + { + "startTime": 10876.06, + "name": "lh:audit:screenshot-thumbnails", + "duration": 2.61, + "entryType": "measure" + }, + { + "startTime": 10878.73, + "name": "lh:audit:final-screenshot", + "duration": 4.09, + "entryType": "measure" + }, + { + "startTime": 10878.94, + "name": "lh:computed:Screenshots", + "duration": 3.69, + "entryType": "measure" + }, + { + "startTime": 10884.2, + "name": "lh:audit:total-blocking-time", + "duration": 6.71, + "entryType": "measure" + }, + { + "startTime": 10885.64, + "name": "lh:computed:TotalBlockingTime", + "duration": 3.76, + "entryType": "measure" + }, + { + "startTime": 10885.94, + "name": "lh:computed:LanternTotalBlockingTime", + "duration": 3.44, + "entryType": "measure" + }, + { + "startTime": 10886.15, + "name": "lh:computed:LanternInteractive", + "duration": 1.5, + "entryType": "measure" + }, + { + "startTime": 10891.74, + "name": "lh:audit:max-potential-fid", + "duration": 11.23, + "entryType": "measure" + }, + { + "startTime": 10893.66, + "name": "lh:computed:MaxPotentialFID", + "duration": 4.7, + "entryType": "measure" + }, + { + "startTime": 10894.04, + "name": "lh:computed:LanternMaxPotentialFID", + "duration": 4.29, + "entryType": "measure" + }, + { + "startTime": 10903.3, + "name": "lh:audit:cumulative-layout-shift", + "duration": 2.46, + "entryType": "measure" + }, + { + "startTime": 10906.32, + "name": "lh:audit:errors-in-console", + "duration": 6.58, + "entryType": "measure" + }, + { + "startTime": 10907.29, + "name": "lh:computed:JSBundles", + "duration": 0.7, + "entryType": "measure" + }, + { + "startTime": 10913.7, + "name": "lh:audit:server-response-time", + "duration": 2.87, + "entryType": "measure" + }, + { + "startTime": 10916.83, + "name": "lh:audit:interactive", + "duration": 1.41, + "entryType": "measure" + }, + { + "startTime": 10917.23, + "name": "lh:computed:Interactive", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 10918.46, + "name": "lh:audit:user-timings", + "duration": 2.05, + "entryType": "measure" + }, + { + "startTime": 10918.77, + "name": "lh:computed:UserTimings", + "duration": 0.87, + "entryType": "measure" + }, + { + "startTime": 10920.74, + "name": "lh:audit:critical-request-chains", + "duration": 8.25, + "entryType": "measure" + }, + { + "startTime": 10921.26, + "name": "lh:computed:CriticalRequestChains", + "duration": 0.62, + "entryType": "measure" + }, + { + "startTime": 10929.91, + "name": "lh:audit:redirects", + "duration": 3.67, + "entryType": "measure" + }, + { + "startTime": 10934.05, + "name": "lh:audit:image-aspect-ratio", + "duration": 2.73, + "entryType": "measure" + }, + { + "startTime": 10937.27, + "name": "lh:audit:image-size-responsive", + "duration": 3.12, + "entryType": "measure" + }, + { + "startTime": 10937.91, + "name": "lh:computed:ImageRecords", + "duration": 0.58, + "entryType": "measure" + }, + { + "startTime": 10941.33, + "name": "lh:audit:deprecations", + "duration": 2.7, + "entryType": "measure" + }, + { + "startTime": 10944.53, + "name": "lh:audit:third-party-cookies", + "duration": 2.05, + "entryType": "measure" + }, + { + "startTime": 10947.26, + "name": "lh:audit:mainthread-work-breakdown", + "duration": 12.01, + "entryType": "measure" + }, + { + "startTime": 10948.23, + "name": "lh:computed:MainThreadTasks", + "duration": 7.66, + "entryType": "measure" + }, + { + "startTime": 10961.69, + "name": "lh:audit:bootup-time", + "duration": 16.02, + "entryType": "measure" + }, + { + "startTime": 10964.98, + "name": "lh:computed:TBTImpactTasks", + "duration": 7.28, + "entryType": "measure" + }, + { + "startTime": 10978.1, + "name": "lh:audit:uses-rel-preconnect", + "duration": 3.24, + "entryType": "measure" + }, + { + "startTime": 10981.9, + "name": "lh:audit:font-display", + "duration": 3.67, + "entryType": "measure" + }, + { + "startTime": 10985.61, + "name": "lh:audit:diagnostics", + "duration": 1.2, + "entryType": "measure" + }, + { + "startTime": 10986.84, + "name": "lh:audit:network-requests", + "duration": 8.69, + "entryType": "measure" + }, + { + "startTime": 10987.16, + "name": "lh:computed:EntityClassification", + "duration": 3.64, + "entryType": "measure" + }, + { + "startTime": 10998.87, + "name": "lh:audit:network-rtt", + "duration": 1.36, + "entryType": "measure" + }, + { + "startTime": 11000.64, + "name": "lh:audit:network-server-latency", + "duration": 1.18, + "entryType": "measure" + }, + { + "startTime": 11001.84, + "name": "lh:audit:main-thread-tasks", + "duration": 0.26, + "entryType": "measure" + }, + { + "startTime": 11002.12, + "name": "lh:audit:metrics", + "duration": 2.96, + "entryType": "measure" + }, + { + "startTime": 11002.34, + "name": "lh:computed:TimingSummary", + "duration": 2.54, + "entryType": "measure" + }, + { + "startTime": 11002.71, + "name": "lh:computed:FirstContentfulPaintAllFrames", + "duration": 0.1, + "entryType": "measure" + }, + { + "startTime": 11002.86, + "name": "lh:computed:LargestContentfulPaintAllFrames", + "duration": 0.12, + "entryType": "measure" + }, + { + "startTime": 11003.07, + "name": "lh:computed:LCPBreakdown", + "duration": 1.04, + "entryType": "measure" + }, + { + "startTime": 11003.23, + "name": "lh:computed:TimeToFirstByte", + "duration": 0.18, + "entryType": "measure" + }, + { + "startTime": 11003.42, + "name": "lh:computed:LCPImageRecord", + "duration": 0.67, + "entryType": "measure" + }, + { + "startTime": 11005.1, + "name": "lh:audit:resource-summary", + "duration": 1.78, + "entryType": "measure" + }, + { + "startTime": 11005.32, + "name": "lh:computed:ResourceSummary", + "duration": 0.59, + "entryType": "measure" + }, + { + "startTime": 11007.4, + "name": "lh:audit:third-party-summary", + "duration": 5.97, + "entryType": "measure" + }, + { + "startTime": 11013.83, + "name": "lh:audit:third-party-facades", + "duration": 2.8, + "entryType": "measure" + }, + { + "startTime": 11016.9, + "name": "lh:audit:largest-contentful-paint-element", + "duration": 2.08, + "entryType": "measure" + }, + { + "startTime": 11019.32, + "name": "lh:audit:lcp-lazy-loaded", + "duration": 1.13, + "entryType": "measure" + }, + { + "startTime": 11020.65, + "name": "lh:audit:layout-shifts", + "duration": 2.15, + "entryType": "measure" + }, + { + "startTime": 11023.44, + "name": "lh:audit:long-tasks", + "duration": 5.25, + "entryType": "measure" + }, + { + "startTime": 11029.04, + "name": "lh:audit:non-composited-animations", + "duration": 1.8, + "entryType": "measure" + }, + { + "startTime": 11031.32, + "name": "lh:audit:unsized-images", + "duration": 2.25, + "entryType": "measure" + }, + { + "startTime": 11033.95, + "name": "lh:audit:valid-source-maps", + "duration": 1.94, + "entryType": "measure" + }, + { + "startTime": 11036.34, + "name": "lh:audit:prioritize-lcp-image", + "duration": 2.21, + "entryType": "measure" + }, + { + "startTime": 11038.98, + "name": "lh:audit:csp-xss", + "duration": 3.02, + "entryType": "measure" + }, + { + "startTime": 11043.37, + "name": "lh:audit:has-hsts", + "duration": 2.84, + "entryType": "measure" + }, + { + "startTime": 11047.18, + "name": "lh:audit:origin-isolation", + "duration": 3.34, + "entryType": "measure" + }, + { + "startTime": 11050.94, + "name": "lh:audit:clickjacking-mitigation", + "duration": 2.33, + "entryType": "measure" + }, + { + "startTime": 11053.95, + "name": "lh:audit:trusted-types-xss", + "duration": 2.29, + "entryType": "measure" + }, + { + "startTime": 11056.31, + "name": "lh:audit:script-treemap-data", + "duration": 26.29, + "entryType": "measure" + }, + { + "startTime": 11057.51, + "name": "lh:computed:ModuleDuplication", + "duration": 0.86, + "entryType": "measure" + }, + { + "startTime": 11058.57, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 1.67, + "entryType": "measure" + }, + { + "startTime": 11060.45, + "name": "lh:computed:UnusedJavascriptSummary", + "duration": 21.96, + "entryType": "measure" + }, + { + "startTime": 11083.19, + "name": "lh:audit:accesskeys", + "duration": 1.83, + "entryType": "measure" + }, + { + "startTime": 11085.36, + "name": "lh:audit:aria-allowed-attr", + "duration": 4.09, + "entryType": "measure" + }, + { + "startTime": 11090.19, + "name": "lh:audit:aria-allowed-role", + "duration": 5.1, + "entryType": "measure" + }, + { + "startTime": 11095.59, + "name": "lh:audit:aria-command-name", + "duration": 1.63, + "entryType": "measure" + }, + { + "startTime": 11098.02, + "name": "lh:audit:aria-conditional-attr", + "duration": 4.74, + "entryType": "measure" + }, + { + "startTime": 11103.2, + "name": "lh:audit:aria-deprecated-role", + "duration": 8.84, + "entryType": "measure" + }, + { + "startTime": 11112.79, + "name": "lh:audit:aria-dialog-name", + "duration": 3.02, + "entryType": "measure" + }, + { + "startTime": 11116.33, + "name": "lh:audit:aria-hidden-body", + "duration": 4.09, + "entryType": "measure" + }, + { + "startTime": 11120.69, + "name": "lh:audit:aria-hidden-focus", + "duration": 4.76, + "entryType": "measure" + }, + { + "startTime": 11126.61, + "name": "lh:audit:aria-input-field-name", + "duration": 1.62, + "entryType": "measure" + }, + { + "startTime": 11128.5, + "name": "lh:audit:aria-meter-name", + "duration": 1.55, + "entryType": "measure" + }, + { + "startTime": 11130.57, + "name": "lh:audit:aria-progressbar-name", + "duration": 2.67, + "entryType": "measure" + }, + { + "startTime": 11133.68, + "name": "lh:audit:aria-prohibited-attr", + "duration": 3.55, + "entryType": "measure" + }, + { + "startTime": 11137.47, + "name": "lh:audit:aria-required-attr", + "duration": 5.78, + "entryType": "measure" + }, + { + "startTime": 11143.65, + "name": "lh:audit:aria-required-children", + "duration": 2.72, + "entryType": "measure" + }, + { + "startTime": 11147.11, + "name": "lh:audit:aria-required-parent", + "duration": 2.21, + "entryType": "measure" + }, + { + "startTime": 11149.55, + "name": "lh:audit:aria-roles", + "duration": 3.26, + "entryType": "measure" + }, + { + "startTime": 11153.06, + "name": "lh:audit:aria-text", + "duration": 1.37, + "entryType": "measure" + }, + { + "startTime": 11154.65, + "name": "lh:audit:aria-toggle-field-name", + "duration": 1.53, + "entryType": "measure" + }, + { + "startTime": 11156.44, + "name": "lh:audit:aria-tooltip-name", + "duration": 3.84, + "entryType": "measure" + }, + { + "startTime": 11160.68, + "name": "lh:audit:aria-treeitem-name", + "duration": 3.18, + "entryType": "measure" + }, + { + "startTime": 11164.34, + "name": "lh:audit:aria-valid-attr-value", + "duration": 3.59, + "entryType": "measure" + }, + { + "startTime": 11168.18, + "name": "lh:audit:aria-valid-attr", + "duration": 8.75, + "entryType": "measure" + }, + { + "startTime": 11177.26, + "name": "lh:audit:button-name", + "duration": 5.62, + "entryType": "measure" + }, + { + "startTime": 11183.31, + "name": "lh:audit:bypass", + "duration": 3.26, + "entryType": "measure" + }, + { + "startTime": 11186.81, + "name": "lh:audit:color-contrast", + "duration": 2.98, + "entryType": "measure" + }, + { + "startTime": 11190.32, + "name": "lh:audit:definition-list", + "duration": 3.52, + "entryType": "measure" + }, + { + "startTime": 11194.1, + "name": "lh:audit:dlitem", + "duration": 1.92, + "entryType": "measure" + }, + { + "startTime": 11196.53, + "name": "lh:audit:document-title", + "duration": 4.93, + "entryType": "measure" + }, + { + "startTime": 11201.7, + "name": "lh:audit:duplicate-id-aria", + "duration": 3, + "entryType": "measure" + }, + { + "startTime": 11205.04, + "name": "lh:audit:empty-heading", + "duration": 6.13, + "entryType": "measure" + }, + { + "startTime": 11211.7, + "name": "lh:audit:form-field-multiple-labels", + "duration": 4.92, + "entryType": "measure" + }, + { + "startTime": 11216.97, + "name": "lh:audit:frame-title", + "duration": 2.39, + "entryType": "measure" + }, + { + "startTime": 11219.6, + "name": "lh:audit:heading-order", + "duration": 14.99, + "entryType": "measure" + }, + { + "startTime": 11234.9, + "name": "lh:audit:html-has-lang", + "duration": 5.7, + "entryType": "measure" + }, + { + "startTime": 11241.26, + "name": "lh:audit:html-lang-valid", + "duration": 7.29, + "entryType": "measure" + }, + { + "startTime": 11249.31, + "name": "lh:audit:html-xml-lang-mismatch", + "duration": 4.18, + "entryType": "measure" + }, + { + "startTime": 11253.88, + "name": "lh:audit:identical-links-same-purpose", + "duration": 7.1, + "entryType": "measure" + }, + { + "startTime": 11261.51, + "name": "lh:audit:image-alt", + "duration": 7.26, + "entryType": "measure" + }, + { + "startTime": 11269.28, + "name": "lh:audit:image-redundant-alt", + "duration": 7.07, + "entryType": "measure" + }, + { + "startTime": 11276.92, + "name": "lh:audit:input-button-name", + "duration": 3.72, + "entryType": "measure" + }, + { + "startTime": 11281.1, + "name": "lh:audit:input-image-alt", + "duration": 3.9, + "entryType": "measure" + }, + { + "startTime": 11285.48, + "name": "lh:audit:label-content-name-mismatch", + "duration": 19.1, + "entryType": "measure" + }, + { + "startTime": 11305.02, + "name": "lh:audit:label", + "duration": 8.02, + "entryType": "measure" + }, + { + "startTime": 11313.79, + "name": "lh:audit:landmark-one-main", + "duration": 5.07, + "entryType": "measure" + }, + { + "startTime": 11319.08, + "name": "lh:audit:link-name", + "duration": 3.99, + "entryType": "measure" + }, + { + "startTime": 11323.72, + "name": "lh:audit:link-in-text-block", + "duration": 3.27, + "entryType": "measure" + }, + { + "startTime": 11327.37, + "name": "lh:audit:list", + "duration": 3.72, + "entryType": "measure" + }, + { + "startTime": 11331.76, + "name": "lh:audit:listitem", + "duration": 3.22, + "entryType": "measure" + }, + { + "startTime": 11335.32, + "name": "lh:audit:meta-refresh", + "duration": 2.19, + "entryType": "measure" + }, + { + "startTime": 11337.74, + "name": "lh:audit:meta-viewport", + "duration": 4.19, + "entryType": "measure" + }, + { + "startTime": 11342.24, + "name": "lh:audit:object-alt", + "duration": 8.38, + "entryType": "measure" + }, + { + "startTime": 11350.85, + "name": "lh:audit:select-name", + "duration": 2.24, + "entryType": "measure" + }, + { + "startTime": 11353.38, + "name": "lh:audit:skip-link", + "duration": 4.18, + "entryType": "measure" + }, + { + "startTime": 11358.2, + "name": "lh:audit:tabindex", + "duration": 3.52, + "entryType": "measure" + }, + { + "startTime": 11362.11, + "name": "lh:audit:table-duplicate-name", + "duration": 2.46, + "entryType": "measure" + }, + { + "startTime": 11364.81, + "name": "lh:audit:table-fake-caption", + "duration": 2.51, + "entryType": "measure" + }, + { + "startTime": 11367.53, + "name": "lh:audit:target-size", + "duration": 2.79, + "entryType": "measure" + }, + { + "startTime": 11370.54, + "name": "lh:audit:td-has-header", + "duration": 2.94, + "entryType": "measure" + }, + { + "startTime": 11374.21, + "name": "lh:audit:td-headers-attr", + "duration": 3.65, + "entryType": "measure" + }, + { + "startTime": 11378.1, + "name": "lh:audit:th-has-data-cells", + "duration": 7.81, + "entryType": "measure" + }, + { + "startTime": 11386.18, + "name": "lh:audit:valid-lang", + "duration": 3.02, + "entryType": "measure" + }, + { + "startTime": 11389.76, + "name": "lh:audit:video-caption", + "duration": 3.9, + "entryType": "measure" + }, + { + "startTime": 11393.69, + "name": "lh:audit:custom-controls-labels", + "duration": 0.11, + "entryType": "measure" + }, + { + "startTime": 11393.83, + "name": "lh:audit:custom-controls-roles", + "duration": 0.02, + "entryType": "measure" + }, + { + "startTime": 11393.85, + "name": "lh:audit:focus-traps", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11393.87, + "name": "lh:audit:focusable-controls", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11393.89, + "name": "lh:audit:interactive-element-affordance", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11393.91, + "name": "lh:audit:logical-tab-order", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11393.92, + "name": "lh:audit:managed-focus", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11393.94, + "name": "lh:audit:offscreen-content-hidden", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11393.96, + "name": "lh:audit:use-landmarks", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11393.97, + "name": "lh:audit:visual-order-follows-dom", + "duration": 0.01, + "entryType": "measure" + }, + { + "startTime": 11394.58, + "name": "lh:audit:uses-long-cache-ttl", + "duration": 2, + "entryType": "measure" + }, + { + "startTime": 11396.79, + "name": "lh:audit:total-byte-weight", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 11398.34, + "name": "lh:audit:offscreen-images", + "duration": 3.49, + "entryType": "measure" + }, + { + "startTime": 11402.02, + "name": "lh:audit:render-blocking-resources", + "duration": 3.06, + "entryType": "measure" + }, + { + "startTime": 11402.65, + "name": "lh:computed:UnusedCSS", + "duration": 0.4, + "entryType": "measure" + }, + { + "startTime": 11403.1, + "name": "lh:computed:NavigationInsights", + "duration": 0.16, + "entryType": "measure" + }, + { + "startTime": 11403.32, + "name": "lh:computed:FirstContentfulPaint", + "duration": 0.19, + "entryType": "measure" + }, + { + "startTime": 11405.35, + "name": "lh:audit:unminified-css", + "duration": 9.52, + "entryType": "measure" + }, + { + "startTime": 11415.05, + "name": "lh:audit:unminified-javascript", + "duration": 44.95, + "entryType": "measure" + }, + { + "startTime": 11460.35, + "name": "lh:audit:unused-css-rules", + "duration": 2.65, + "entryType": "measure" + }, + { + "startTime": 11463.25, + "name": "lh:audit:unused-javascript", + "duration": 3.73, + "entryType": "measure" + }, + { + "startTime": 11467.29, + "name": "lh:audit:modern-image-formats", + "duration": 3.16, + "entryType": "measure" + }, + { + "startTime": 11470.78, + "name": "lh:audit:uses-optimized-images", + "duration": 3.37, + "entryType": "measure" + }, + { + "startTime": 11474.58, + "name": "lh:audit:uses-text-compression", + "duration": 3.37, + "entryType": "measure" + }, + { + "startTime": 11478.37, + "name": "lh:audit:uses-responsive-images", + "duration": 2.17, + "entryType": "measure" + }, + { + "startTime": 11480.73, + "name": "lh:audit:efficient-animated-content", + "duration": 2.39, + "entryType": "measure" + }, + { + "startTime": 11483.41, + "name": "lh:audit:duplicated-javascript", + "duration": 2.95, + "entryType": "measure" + }, + { + "startTime": 11486.66, + "name": "lh:audit:legacy-javascript", + "duration": 50.21, + "entryType": "measure" + }, + { + "startTime": 11537.36, + "name": "lh:audit:doctype", + "duration": 2.3, + "entryType": "measure" + }, + { + "startTime": 11540.18, + "name": "lh:audit:charset", + "duration": 4.54, + "entryType": "measure" + }, + { + "startTime": 11545.2, + "name": "lh:audit:dom-size", + "duration": 3.89, + "entryType": "measure" + }, + { + "startTime": 11549.61, + "name": "lh:audit:geolocation-on-start", + "duration": 1.48, + "entryType": "measure" + }, + { + "startTime": 11551.53, + "name": "lh:audit:inspector-issues", + "duration": 1.8, + "entryType": "measure" + }, + { + "startTime": 11553.72, + "name": "lh:audit:no-document-write", + "duration": 1.75, + "entryType": "measure" + }, + { + "startTime": 11555.79, + "name": "lh:audit:js-libraries", + "duration": 1.38, + "entryType": "measure" + }, + { + "startTime": 11557.63, + "name": "lh:audit:notification-on-start", + "duration": 1.68, + "entryType": "measure" + }, + { + "startTime": 11559.91, + "name": "lh:audit:paste-preventing-inputs", + "duration": 1.08, + "entryType": "measure" + }, + { + "startTime": 11561.22, + "name": "lh:audit:uses-http2", + "duration": 5.59, + "entryType": "measure" + }, + { + "startTime": 11567.1, + "name": "lh:audit:uses-passive-event-listeners", + "duration": 1.17, + "entryType": "measure" + }, + { + "startTime": 11568.65, + "name": "lh:audit:meta-description", + "duration": 1.03, + "entryType": "measure" + }, + { + "startTime": 11570.06, + "name": "lh:audit:http-status-code", + "duration": 1.59, + "entryType": "measure" + }, + { + "startTime": 11572.14, + "name": "lh:audit:font-size", + "duration": 3.73, + "entryType": "measure" + }, + { + "startTime": 11576.44, + "name": "lh:audit:link-text", + "duration": 2.18, + "entryType": "measure" + }, + { + "startTime": 11579.1, + "name": "lh:audit:crawlable-anchors", + "duration": 1.37, + "entryType": "measure" + }, + { + "startTime": 11580.75, + "name": "lh:audit:is-crawlable", + "duration": 1.97, + "entryType": "measure" + }, + { + "startTime": 11582.97, + "name": "lh:audit:robots-txt", + "duration": 1.92, + "entryType": "measure" + }, + { + "startTime": 11585.3, + "name": "lh:audit:hreflang", + "duration": 1.2, + "entryType": "measure" + }, + { + "startTime": 11586.77, + "name": "lh:audit:canonical", + "duration": 1.3, + "entryType": "measure" + }, + { + "startTime": 11588.31, + "name": "lh:audit:structured-data", + "duration": 0.98, + "entryType": "measure" + }, + { + "startTime": 11589.76, + "name": "lh:audit:bf-cache", + "duration": 1.87, + "entryType": "measure" + }, + { + "startTime": 11592.12, + "name": "lh:audit:cache-insight", + "duration": 2.22, + "entryType": "measure" + }, + { + "startTime": 11594.6, + "name": "lh:audit:cls-culprits-insight", + "duration": 1.7, + "entryType": "measure" + }, + { + "startTime": 11596.53, + "name": "lh:audit:document-latency-insight", + "duration": 0.87, + "entryType": "measure" + }, + { + "startTime": 11597.63, + "name": "lh:audit:dom-size-insight", + "duration": 1.12, + "entryType": "measure" + }, + { + "startTime": 11598.98, + "name": "lh:audit:duplicated-javascript-insight", + "duration": 0.92, + "entryType": "measure" + }, + { + "startTime": 11600.14, + "name": "lh:audit:font-display-insight", + "duration": 0.97, + "entryType": "measure" + }, + { + "startTime": 11601.47, + "name": "lh:audit:forced-reflow-insight", + "duration": 1.22, + "entryType": "measure" + }, + { + "startTime": 11602.93, + "name": "lh:audit:image-delivery-insight", + "duration": 1.01, + "entryType": "measure" + }, + { + "startTime": 11604.22, + "name": "lh:audit:inp-breakdown-insight", + "duration": 1.57, + "entryType": "measure" + }, + { + "startTime": 11606.3, + "name": "lh:audit:lcp-breakdown-insight", + "duration": 2.72, + "entryType": "measure" + }, + { + "startTime": 11609.52, + "name": "lh:audit:lcp-discovery-insight", + "duration": 2.67, + "entryType": "measure" + }, + { + "startTime": 11619.11, + "name": "lh:audit:legacy-javascript-insight", + "duration": 2.5, + "entryType": "measure" + }, + { + "startTime": 11622.11, + "name": "lh:audit:modern-http-insight", + "duration": 2.27, + "entryType": "measure" + }, + { + "startTime": 11625.26, + "name": "lh:audit:network-dependency-tree-insight", + "duration": 3.29, + "entryType": "measure" + }, + { + "startTime": 11629.12, + "name": "lh:audit:render-blocking-insight", + "duration": 2.96, + "entryType": "measure" + }, + { + "startTime": 11632.57, + "name": "lh:audit:third-parties-insight", + "duration": 17.69, + "entryType": "measure" + }, + { + "startTime": 11650.7, + "name": "lh:audit:viewport-insight", + "duration": 1.78, + "entryType": "measure" + }, + { + "startTime": 11652.58, + "name": "lh:runner:generate", + "duration": 1.19, + "entryType": "measure" + } + ], + "total": 8317.19 + }, + "i18n": { + "rendererFormattedStrings": { + "calculatorLink": "See calculator.", + "collapseView": "Collapse view", + "crcInitialNavigation": "Initial Navigation", + "crcLongestDurationLabel": "Maximum critical path latency:", + "dropdownCopyJSON": "Copy JSON", + "dropdownDarkTheme": "Toggle Dark Theme", + "dropdownPrintExpanded": "Print Expanded", + "dropdownPrintSummary": "Print Summary", + "dropdownSaveGist": "Save as Gist", + "dropdownSaveHTML": "Save as HTML", + "dropdownSaveJSON": "Save as JSON", + "dropdownViewUnthrottledTrace": "View Unthrottled Trace", + "dropdownViewer": "Open in Viewer", + "errorLabel": "Error!", + "errorMissingAuditInfo": "Report error: no audit information", + "expandView": "Expand view", + "firstPartyChipLabel": "1st party", + "footerIssue": "File an issue", + "goBackToAudits": "Go back to audits", + "hide": "Hide", + "insightsNotice": "Later this year, insights will replace performance audits. [Learn more and provide feedback here](https://github.com/GoogleChrome/lighthouse/discussions/16462).", + "labDataTitle": "Lab Data", + "lsPerformanceCategoryDescription": "[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.", + "manualAuditsGroupTitle": "Additional items to manually check", + "notApplicableAuditsGroupTitle": "Not applicable", + "openInANewTabTooltip": "Open in a new tab", + "opportunityResourceColumnLabel": "Opportunity", + "opportunitySavingsColumnLabel": "Estimated Savings", + "passedAuditsGroupTitle": "Passed audits", + "runtimeAnalysisWindow": "Initial page load", + "runtimeAnalysisWindowSnapshot": "Point-in-time snapshot", + "runtimeAnalysisWindowTimespan": "User interactions timespan", + "runtimeCustom": "Custom throttling", + "runtimeDesktopEmulation": "Emulated Desktop", + "runtimeMobileEmulation": "Emulated Moto G Power", + "runtimeNoEmulation": "No emulation", + "runtimeSettingsAxeVersion": "Axe version", + "runtimeSettingsBenchmark": "Unthrottled CPU/Memory Power", + "runtimeSettingsCPUThrottling": "CPU throttling", + "runtimeSettingsDevice": "Device", + "runtimeSettingsNetworkThrottling": "Network throttling", + "runtimeSettingsScreenEmulation": "Screen emulation", + "runtimeSettingsUANetwork": "User agent (network)", + "runtimeSingleLoad": "Single page session", + "runtimeSingleLoadTooltip": "This data is taken from a single page session, as opposed to field data summarizing many sessions.", + "runtimeSlow4g": "Slow 4G throttling", + "runtimeUnknown": "Unknown", + "show": "Show", + "showRelevantAudits": "Show audits relevant to:", + "snippetCollapseButtonLabel": "Collapse snippet", + "snippetExpandButtonLabel": "Expand snippet", + "thirdPartyResourcesLabel": "Show 3rd-party resources", + "throttlingProvided": "Provided by environment", + "toplevelWarningsMessage": "There were issues affecting this run of Lighthouse:", + "tryInsights": "Try insights", + "unattributable": "Unattributable", + "varianceDisclaimer": "Values are estimated and may vary. The [performance score is calculated](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/) directly from these metrics.", + "viewTraceLabel": "View Trace", + "viewTreemapLabel": "View Treemap", + "warningAuditsGroupTitle": "Passed audits but with warnings", + "warningHeader": "Warnings: " + }, + "icuMessagePaths": { + "core/audits/is-on-https.js | title": ["audits[is-on-https].title"], + "core/audits/is-on-https.js | description": [ + "audits[is-on-https].description" + ], + "core/audits/is-on-https.js | columnInsecureURL": [ + "audits[is-on-https].details.headings[0].label" + ], + "core/audits/is-on-https.js | columnResolution": [ + "audits[is-on-https].details.headings[1].label" + ], + "core/audits/redirects-http.js | title": ["audits[redirects-http].title"], + "core/audits/redirects-http.js | description": [ + "audits[redirects-http].description" + ], + "core/audits/viewport.js | title": ["audits.viewport.title"], + "core/audits/viewport.js | description": ["audits.viewport.description"], + "core/lib/i18n/i18n.js | firstContentfulPaintMetric": [ + "audits[first-contentful-paint].title" + ], + "core/audits/metrics/first-contentful-paint.js | description": [ + "audits[first-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | seconds": [ + { + "values": { + "timeInMs": 2761.3151 + }, + "path": "audits[first-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 3139.4726499999997 + }, + "path": "audits[largest-contentful-paint].displayValue" + }, + { + "values": { + "timeInMs": 2761.3151 + }, + "path": "audits[speed-index].displayValue" + }, + { + "values": { + "timeInMs": 3181.5517525 + }, + "path": "audits.interactive.displayValue" + }, + { + "values": { + "timeInMs": 1361.2440000000017 + }, + "path": "audits[mainthread-work-breakdown].displayValue" + }, + { + "values": { + "timeInMs": 311.1200000000007 + }, + "path": "audits[bootup-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | largestContentfulPaintMetric": [ + "audits[largest-contentful-paint].title" + ], + "core/audits/metrics/largest-contentful-paint.js | description": [ + "audits[largest-contentful-paint].description" + ], + "core/lib/i18n/i18n.js | firstMeaningfulPaintMetric": [ + "audits[first-meaningful-paint].title" + ], + "core/audits/metrics/first-meaningful-paint.js | description": [ + "audits[first-meaningful-paint].description" + ], + "core/lib/i18n/i18n.js | speedIndexMetric": ["audits[speed-index].title"], + "core/audits/metrics/speed-index.js | description": [ + "audits[speed-index].description" + ], + "core/lib/i18n/i18n.js | totalBlockingTimeMetric": [ + "audits[total-blocking-time].title" + ], + "core/audits/metrics/total-blocking-time.js | description": [ + "audits[total-blocking-time].description" + ], + "core/lib/i18n/i18n.js | ms": [ + { + "values": { + "timeInMs": 30 + }, + "path": "audits[total-blocking-time].displayValue" + }, + { + "values": { + "timeInMs": 80 + }, + "path": "audits[max-potential-fid].displayValue" + }, + { + "values": { + "timeInMs": 0.05145 + }, + "path": "audits[network-rtt].displayValue" + }, + { + "values": { + "timeInMs": 3.15755 + }, + "path": "audits[network-server-latency].displayValue" + }, + { + "values": { + "timeInMs": 3139.4726499999997 + }, + "path": "audits[largest-contentful-paint-element].displayValue" + } + ], + "core/lib/i18n/i18n.js | maxPotentialFIDMetric": [ + "audits[max-potential-fid].title" + ], + "core/audits/metrics/max-potential-fid.js | description": [ + "audits[max-potential-fid].description" + ], + "core/lib/i18n/i18n.js | cumulativeLayoutShiftMetric": [ + "audits[cumulative-layout-shift].title" + ], + "core/audits/metrics/cumulative-layout-shift.js | description": [ + "audits[cumulative-layout-shift].description" + ], + "core/audits/errors-in-console.js | failureTitle": [ + "audits[errors-in-console].title" + ], + "core/audits/errors-in-console.js | description": [ + "audits[errors-in-console].description" + ], + "core/lib/i18n/i18n.js | columnSource": [ + "audits[errors-in-console].details.headings[0].label", + "audits.deprecations.details.headings[1].label", + "audits[geolocation-on-start].details.headings[0].label", + "audits[no-document-write].details.headings[0].label", + "audits[notification-on-start].details.headings[0].label", + "audits[uses-passive-event-listeners].details.headings[0].label", + "audits[font-size].details.headings[0].label", + "audits[forced-reflow-insight].details.items[0].headings[0].label" + ], + "core/lib/i18n/i18n.js | columnDescription": [ + "audits[errors-in-console].details.headings[1].label", + "audits[csp-xss].details.headings[0].label", + "audits[has-hsts].details.headings[0].label", + "audits[origin-isolation].details.headings[0].label", + "audits[clickjacking-mitigation].details.headings[0].label", + "audits[trusted-types-xss].details.headings[0].label" + ], + "core/audits/server-response-time.js | title": [ + "audits[server-response-time].title" + ], + "core/audits/server-response-time.js | description": [ + "audits[server-response-time].description" + ], + "core/audits/server-response-time.js | displayValue": [ + { + "values": { + "timeInMs": 1.468 + }, + "path": "audits[server-response-time].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnURL": [ + "audits[server-response-time].details.headings[0].label", + "audits[image-aspect-ratio].details.headings[1].label", + "audits[image-size-responsive].details.headings[1].label", + "audits[third-party-cookies].details.headings[1].label", + "audits[bootup-time].details.headings[0].label", + "audits[font-display].details.headings[0].label", + "audits[network-rtt].details.headings[0].label", + "audits[network-server-latency].details.headings[0].label", + "audits[long-tasks].details.headings[0].label", + "audits[unsized-images].details.headings[1].label", + "audits[valid-source-maps].details.headings[0].label", + "audits[uses-long-cache-ttl].details.headings[0].label", + "audits[total-byte-weight].details.headings[0].label", + "audits[render-blocking-resources].details.headings[0].label", + "audits[unused-javascript].details.headings[0].label", + "audits[font-display-insight].details.headings[0].label", + "audits[image-delivery-insight].details.headings[0].label", + "audits[legacy-javascript-insight].details.headings[0].label", + "audits[modern-http-insight].details.headings[0].label", + "audits[render-blocking-insight].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnTimeSpent": [ + "audits[server-response-time].details.headings[1].label", + "audits[mainthread-work-breakdown].details.headings[1].label", + "audits[network-rtt].details.headings[1].label", + "audits[network-server-latency].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | interactiveMetric": ["audits.interactive.title"], + "core/audits/metrics/interactive.js | description": [ + "audits.interactive.description" + ], + "core/audits/user-timings.js | title": ["audits[user-timings].title"], + "core/audits/user-timings.js | description": [ + "audits[user-timings].description" + ], + "core/lib/i18n/i18n.js | columnName": [ + "audits[user-timings].details.headings[0].label", + "audits[third-party-cookies].details.headings[0].label" + ], + "core/audits/user-timings.js | columnType": [ + "audits[user-timings].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnStartTime": [ + "audits[user-timings].details.headings[2].label", + "audits[long-tasks].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnDuration": [ + "audits[user-timings].details.headings[3].label", + "audits[long-tasks].details.headings[2].label", + "audits[render-blocking-resources].details.headings[2].label", + "audits[lcp-breakdown-insight].details.items[0].headings[1].label", + "audits[render-blocking-insight].details.headings[2].label" + ], + "core/audits/critical-request-chains.js | title": [ + "audits[critical-request-chains].title" + ], + "core/audits/critical-request-chains.js | description": [ + "audits[critical-request-chains].description" + ], + "core/audits/critical-request-chains.js | displayValue": [ + { + "values": { + "itemCount": 6 + }, + "path": "audits[critical-request-chains].displayValue" + } + ], + "core/audits/redirects.js | title": ["audits.redirects.title"], + "core/audits/redirects.js | description": [ + "audits.redirects.description" + ], + "core/audits/image-aspect-ratio.js | title": [ + "audits[image-aspect-ratio].title" + ], + "core/audits/image-aspect-ratio.js | description": [ + "audits[image-aspect-ratio].description" + ], + "core/audits/image-aspect-ratio.js | columnDisplayed": [ + "audits[image-aspect-ratio].details.headings[2].label" + ], + "core/audits/image-aspect-ratio.js | columnActual": [ + "audits[image-aspect-ratio].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | title": [ + "audits[image-size-responsive].title" + ], + "core/audits/image-size-responsive.js | description": [ + "audits[image-size-responsive].description" + ], + "core/audits/image-size-responsive.js | columnDisplayed": [ + "audits[image-size-responsive].details.headings[2].label" + ], + "core/audits/image-size-responsive.js | columnActual": [ + "audits[image-size-responsive].details.headings[3].label" + ], + "core/audits/image-size-responsive.js | columnExpected": [ + "audits[image-size-responsive].details.headings[4].label" + ], + "core/audits/deprecations.js | title": ["audits.deprecations.title"], + "core/audits/deprecations.js | description": [ + "audits.deprecations.description" + ], + "core/audits/deprecations.js | columnDeprecate": [ + "audits.deprecations.details.headings[0].label" + ], + "core/audits/third-party-cookies.js | title": [ + "audits[third-party-cookies].title" + ], + "core/audits/third-party-cookies.js | description": [ + "audits[third-party-cookies].description" + ], + "core/audits/mainthread-work-breakdown.js | title": [ + "audits[mainthread-work-breakdown].title" + ], + "core/audits/mainthread-work-breakdown.js | description": [ + "audits[mainthread-work-breakdown].description" + ], + "core/audits/mainthread-work-breakdown.js | columnCategory": [ + "audits[mainthread-work-breakdown].details.headings[0].label" + ], + "core/audits/bootup-time.js | title": ["audits[bootup-time].title"], + "core/audits/bootup-time.js | description": [ + "audits[bootup-time].description" + ], + "core/audits/bootup-time.js | columnTotal": [ + "audits[bootup-time].details.headings[1].label" + ], + "core/audits/bootup-time.js | columnScriptEval": [ + "audits[bootup-time].details.headings[2].label" + ], + "core/audits/bootup-time.js | columnScriptParse": [ + "audits[bootup-time].details.headings[3].label" + ], + "core/audits/uses-rel-preconnect.js | title": [ + "audits[uses-rel-preconnect].title" + ], + "core/audits/uses-rel-preconnect.js | description": [ + "audits[uses-rel-preconnect].description" + ], + "core/audits/font-display.js | title": ["audits[font-display].title"], + "core/audits/font-display.js | description": [ + "audits[font-display].description" + ], + "core/lib/i18n/i18n.js | columnWastedBytes": [ + "audits[font-display].details.headings[1].label", + "audits[unused-javascript].details.headings[2].label", + "audits[font-display-insight].details.headings[1].label", + "audits[image-delivery-insight].details.headings[2].label" + ], + "core/audits/network-rtt.js | title": ["audits[network-rtt].title"], + "core/audits/network-rtt.js | description": [ + "audits[network-rtt].description" + ], + "core/audits/network-server-latency.js | title": [ + "audits[network-server-latency].title" + ], + "core/audits/network-server-latency.js | description": [ + "audits[network-server-latency].description" + ], + "core/lib/i18n/i18n.js | columnResourceType": [ + "audits[resource-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnRequests": [ + "audits[resource-summary].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | columnTransferSize": [ + "audits[resource-summary].details.headings[2].label", + "audits[third-party-summary].details.headings[1].label", + "audits[uses-long-cache-ttl].details.headings[2].label", + "audits[total-byte-weight].details.headings[1].label", + "audits[render-blocking-resources].details.headings[1].label", + "audits[unused-javascript].details.headings[1].label", + "audits[cache-insight].details.headings[2].label", + "audits[render-blocking-insight].details.headings[1].label" + ], + "core/lib/i18n/i18n.js | total": [ + "audits[resource-summary].details.items[0].label", + "audits[cls-culprits-insight].details.items[0].items[0].node.value" + ], + "core/lib/i18n/i18n.js | fontResourceType": [ + "audits[resource-summary].details.items[1].label" + ], + "core/lib/i18n/i18n.js | scriptResourceType": [ + "audits[resource-summary].details.items[2].label" + ], + "core/lib/i18n/i18n.js | imageResourceType": [ + "audits[resource-summary].details.items[3].label" + ], + "core/lib/i18n/i18n.js | stylesheetResourceType": [ + "audits[resource-summary].details.items[4].label" + ], + "core/lib/i18n/i18n.js | otherResourceType": [ + "audits[resource-summary].details.items[5].label" + ], + "core/lib/i18n/i18n.js | documentResourceType": [ + "audits[resource-summary].details.items[6].label" + ], + "core/lib/i18n/i18n.js | mediaResourceType": [ + "audits[resource-summary].details.items[7].label" + ], + "core/lib/i18n/i18n.js | thirdPartyResourceType": [ + "audits[resource-summary].details.items[8].label" + ], + "core/audits/third-party-summary.js | title": [ + "audits[third-party-summary].title" + ], + "core/audits/third-party-summary.js | description": [ + "audits[third-party-summary].description" + ], + "core/audits/third-party-summary.js | displayValue": [ + { + "values": { + "timeInMs": 0 + }, + "path": "audits[third-party-summary].displayValue" + } + ], + "core/audits/third-party-summary.js | columnThirdParty": [ + "audits[third-party-summary].details.headings[0].label" + ], + "core/lib/i18n/i18n.js | columnBlockingTime": [ + "audits[third-party-summary].details.headings[2].label" + ], + "core/audits/third-party-facades.js | title": [ + "audits[third-party-facades].title" + ], + "core/audits/third-party-facades.js | description": [ + "audits[third-party-facades].description" + ], + "core/audits/largest-contentful-paint-element.js | title": [ + "audits[largest-contentful-paint-element].title" + ], + "core/audits/largest-contentful-paint-element.js | description": [ + "audits[largest-contentful-paint-element].description" + ], + "core/lib/i18n/i18n.js | columnElement": [ + "audits[largest-contentful-paint-element].details.items[0].headings[0].label", + "audits[layout-shifts].details.headings[0].label", + "audits[non-composited-animations].details.headings[0].label", + "audits[dom-size].details.headings[1].label", + "audits[cls-culprits-insight].details.items[0].headings[0].label", + "audits[dom-size-insight].details.headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPhase": [ + "audits[largest-contentful-paint-element].details.items[1].headings[0].label" + ], + "core/audits/largest-contentful-paint-element.js | columnPercentOfLCP": [ + "audits[largest-contentful-paint-element].details.items[1].headings[1].label" + ], + "core/audits/largest-contentful-paint-element.js | columnTiming": [ + "audits[largest-contentful-paint-element].details.items[1].headings[2].label" + ], + "core/audits/largest-contentful-paint-element.js | itemTTFB": [ + "audits[largest-contentful-paint-element].details.items[1].items[0].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[1].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemLoadTime": [ + "audits[largest-contentful-paint-element].details.items[1].items[2].phase" + ], + "core/audits/largest-contentful-paint-element.js | itemRenderDelay": [ + "audits[largest-contentful-paint-element].details.items[1].items[3].phase" + ], + "core/audits/lcp-lazy-loaded.js | title": [ + "audits[lcp-lazy-loaded].title" + ], + "core/audits/lcp-lazy-loaded.js | description": [ + "audits[lcp-lazy-loaded].description" + ], + "core/audits/layout-shifts.js | title": ["audits[layout-shifts].title"], + "core/audits/layout-shifts.js | description": [ + "audits[layout-shifts].description" + ], + "core/audits/layout-shifts.js | displayValueShiftsFound": [ + { + "values": { + "shiftCount": 1 + }, + "path": "audits[layout-shifts].displayValue" + } + ], + "core/audits/layout-shifts.js | columnScore": [ + "audits[layout-shifts].details.headings[1].label" + ], + "core/audits/long-tasks.js | title": ["audits[long-tasks].title"], + "core/audits/long-tasks.js | description": [ + "audits[long-tasks].description" + ], + "core/audits/long-tasks.js | displayValue": [ + { + "values": { + "itemCount": 4 + }, + "path": "audits[long-tasks].displayValue" + } + ], + "core/audits/non-composited-animations.js | title": [ + "audits[non-composited-animations].title" + ], + "core/audits/non-composited-animations.js | description": [ + "audits[non-composited-animations].description" + ], + "core/audits/unsized-images.js | failureTitle": [ + "audits[unsized-images].title" + ], + "core/audits/unsized-images.js | description": [ + "audits[unsized-images].description" + ], + "core/audits/valid-source-maps.js | title": [ + "audits[valid-source-maps].title" + ], + "core/audits/valid-source-maps.js | description": [ + "audits[valid-source-maps].description" + ], + "core/audits/valid-source-maps.js | columnMapURL": [ + "audits[valid-source-maps].details.headings[1].label" + ], + "core/audits/prioritize-lcp-image.js | title": [ + "audits[prioritize-lcp-image].title" + ], + "core/audits/prioritize-lcp-image.js | description": [ + "audits[prioritize-lcp-image].description" + ], + "core/audits/csp-xss.js | title": ["audits[csp-xss].title"], + "core/audits/csp-xss.js | description": ["audits[csp-xss].description"], + "core/audits/csp-xss.js | columnDirective": [ + "audits[csp-xss].details.headings[1].label" + ], + "core/audits/csp-xss.js | columnSeverity": [ + "audits[csp-xss].details.headings[2].label" + ], + "core/lib/i18n/i18n.js | itemSeverityHigh": [ + "audits[csp-xss].details.items[0].severity", + "audits[has-hsts].details.items[0].severity", + "audits[origin-isolation].details.items[0].severity", + "audits[clickjacking-mitigation].details.items[0].severity", + "audits[trusted-types-xss].details.items[0].severity" + ], + "core/audits/csp-xss.js | noCsp": [ + "audits[csp-xss].details.items[0].description" + ], + "core/audits/has-hsts.js | title": ["audits[has-hsts].title"], + "core/audits/has-hsts.js | description": ["audits[has-hsts].description"], + "core/audits/has-hsts.js | columnDirective": [ + "audits[has-hsts].details.headings[1].label" + ], + "core/audits/has-hsts.js | columnSeverity": [ + "audits[has-hsts].details.headings[2].label" + ], + "core/audits/has-hsts.js | noHsts": [ + "audits[has-hsts].details.items[0].description" + ], + "core/audits/origin-isolation.js | title": [ + "audits[origin-isolation].title" + ], + "core/audits/origin-isolation.js | description": [ + "audits[origin-isolation].description" + ], + "core/audits/origin-isolation.js | columnDirective": [ + "audits[origin-isolation].details.headings[1].label" + ], + "core/audits/origin-isolation.js | columnSeverity": [ + "audits[origin-isolation].details.headings[2].label" + ], + "core/audits/origin-isolation.js | noCoop": [ + "audits[origin-isolation].details.items[0].description" + ], + "core/audits/clickjacking-mitigation.js | title": [ + "audits[clickjacking-mitigation].title" + ], + "core/audits/clickjacking-mitigation.js | description": [ + "audits[clickjacking-mitigation].description" + ], + "core/audits/clickjacking-mitigation.js | columnSeverity": [ + "audits[clickjacking-mitigation].details.headings[1].label" + ], + "core/audits/clickjacking-mitigation.js | noClickjackingMitigation": [ + "audits[clickjacking-mitigation].details.items[0].description" + ], + "core/audits/trusted-types-xss.js | title": [ + "audits[trusted-types-xss].title" + ], + "core/audits/trusted-types-xss.js | description": [ + "audits[trusted-types-xss].description" + ], + "core/audits/trusted-types-xss.js | columnSeverity": [ + "audits[trusted-types-xss].details.headings[1].label" + ], + "core/audits/trusted-types-xss.js | noTrustedTypesToMitigateXss": [ + "audits[trusted-types-xss].details.items[0].description" + ], + "core/audits/accessibility/accesskeys.js | title": [ + "audits.accesskeys.title" + ], + "core/audits/accessibility/accesskeys.js | description": [ + "audits.accesskeys.description" + ], + "core/audits/accessibility/aria-allowed-attr.js | title": [ + "audits[aria-allowed-attr].title" + ], + "core/audits/accessibility/aria-allowed-attr.js | description": [ + "audits[aria-allowed-attr].description" + ], + "core/lib/i18n/i18n.js | columnFailingElem": [ + "audits[aria-allowed-attr].details.headings[0].label", + "audits[aria-allowed-role].details.headings[0].label", + "audits[aria-conditional-attr].details.headings[0].label", + "audits[aria-deprecated-role].details.headings[0].label", + "audits[aria-hidden-body].details.headings[0].label", + "audits[aria-hidden-focus].details.headings[0].label", + "audits[aria-prohibited-attr].details.headings[0].label", + "audits[aria-required-attr].details.headings[0].label", + "audits[aria-roles].details.headings[0].label", + "audits[aria-valid-attr-value].details.headings[0].label", + "audits[aria-valid-attr].details.headings[0].label", + "audits[button-name].details.headings[0].label", + "audits[color-contrast].details.headings[0].label", + "audits[document-title].details.headings[0].label", + "audits[heading-order].details.headings[0].label", + "audits[html-has-lang].details.headings[0].label", + "audits[html-lang-valid].details.headings[0].label", + "audits[image-alt].details.headings[0].label", + "audits[image-redundant-alt].details.headings[0].label", + "audits[label-content-name-mismatch].details.headings[0].label", + "audits.label.details.headings[0].label", + "audits[link-name].details.headings[0].label", + "audits.list.details.headings[0].label", + "audits.listitem.details.headings[0].label", + "audits[meta-viewport].details.headings[0].label", + "audits[skip-link].details.headings[0].label", + "audits[target-size].details.headings[0].label", + "audits[paste-preventing-inputs].details.headings[0].label" + ], + "core/audits/accessibility/aria-allowed-role.js | title": [ + "audits[aria-allowed-role].title" + ], + "core/audits/accessibility/aria-allowed-role.js | description": [ + "audits[aria-allowed-role].description" + ], + "core/audits/accessibility/aria-command-name.js | title": [ + "audits[aria-command-name].title" + ], + "core/audits/accessibility/aria-command-name.js | description": [ + "audits[aria-command-name].description" + ], + "core/audits/accessibility/aria-conditional-attr.js | title": [ + "audits[aria-conditional-attr].title" + ], + "core/audits/accessibility/aria-conditional-attr.js | description": [ + "audits[aria-conditional-attr].description" + ], + "core/audits/accessibility/aria-deprecated-role.js | title": [ + "audits[aria-deprecated-role].title" + ], + "core/audits/accessibility/aria-deprecated-role.js | description": [ + "audits[aria-deprecated-role].description" + ], + "core/audits/accessibility/aria-dialog-name.js | title": [ + "audits[aria-dialog-name].title" + ], + "core/audits/accessibility/aria-dialog-name.js | description": [ + "audits[aria-dialog-name].description" + ], + "core/audits/accessibility/aria-hidden-body.js | title": [ + "audits[aria-hidden-body].title" + ], + "core/audits/accessibility/aria-hidden-body.js | description": [ + "audits[aria-hidden-body].description" + ], + "core/audits/accessibility/aria-hidden-focus.js | title": [ + "audits[aria-hidden-focus].title" + ], + "core/audits/accessibility/aria-hidden-focus.js | description": [ + "audits[aria-hidden-focus].description" + ], + "core/audits/accessibility/aria-input-field-name.js | title": [ + "audits[aria-input-field-name].title" + ], + "core/audits/accessibility/aria-input-field-name.js | description": [ + "audits[aria-input-field-name].description" + ], + "core/audits/accessibility/aria-meter-name.js | title": [ + "audits[aria-meter-name].title" + ], + "core/audits/accessibility/aria-meter-name.js | description": [ + "audits[aria-meter-name].description" + ], + "core/audits/accessibility/aria-progressbar-name.js | title": [ + "audits[aria-progressbar-name].title" + ], + "core/audits/accessibility/aria-progressbar-name.js | description": [ + "audits[aria-progressbar-name].description" + ], + "core/audits/accessibility/aria-prohibited-attr.js | title": [ + "audits[aria-prohibited-attr].title" + ], + "core/audits/accessibility/aria-prohibited-attr.js | description": [ + "audits[aria-prohibited-attr].description" + ], + "core/audits/accessibility/aria-required-attr.js | title": [ + "audits[aria-required-attr].title" + ], + "core/audits/accessibility/aria-required-attr.js | description": [ + "audits[aria-required-attr].description" + ], + "core/audits/accessibility/aria-required-children.js | title": [ + "audits[aria-required-children].title" + ], + "core/audits/accessibility/aria-required-children.js | description": [ + "audits[aria-required-children].description" + ], + "core/audits/accessibility/aria-required-parent.js | title": [ + "audits[aria-required-parent].title" + ], + "core/audits/accessibility/aria-required-parent.js | description": [ + "audits[aria-required-parent].description" + ], + "core/audits/accessibility/aria-roles.js | title": [ + "audits[aria-roles].title" + ], + "core/audits/accessibility/aria-roles.js | description": [ + "audits[aria-roles].description" + ], + "core/audits/accessibility/aria-text.js | title": [ + "audits[aria-text].title" + ], + "core/audits/accessibility/aria-text.js | description": [ + "audits[aria-text].description" + ], + "core/audits/accessibility/aria-toggle-field-name.js | title": [ + "audits[aria-toggle-field-name].title" + ], + "core/audits/accessibility/aria-toggle-field-name.js | description": [ + "audits[aria-toggle-field-name].description" + ], + "core/audits/accessibility/aria-tooltip-name.js | title": [ + "audits[aria-tooltip-name].title" + ], + "core/audits/accessibility/aria-tooltip-name.js | description": [ + "audits[aria-tooltip-name].description" + ], + "core/audits/accessibility/aria-treeitem-name.js | title": [ + "audits[aria-treeitem-name].title" + ], + "core/audits/accessibility/aria-treeitem-name.js | description": [ + "audits[aria-treeitem-name].description" + ], + "core/audits/accessibility/aria-valid-attr-value.js | title": [ + "audits[aria-valid-attr-value].title" + ], + "core/audits/accessibility/aria-valid-attr-value.js | description": [ + "audits[aria-valid-attr-value].description" + ], + "core/audits/accessibility/aria-valid-attr.js | title": [ + "audits[aria-valid-attr].title" + ], + "core/audits/accessibility/aria-valid-attr.js | description": [ + "audits[aria-valid-attr].description" + ], + "core/audits/accessibility/button-name.js | title": [ + "audits[button-name].title" + ], + "core/audits/accessibility/button-name.js | description": [ + "audits[button-name].description" + ], + "core/audits/accessibility/bypass.js | title": ["audits.bypass.title"], + "core/audits/accessibility/bypass.js | description": [ + "audits.bypass.description" + ], + "core/audits/accessibility/color-contrast.js | title": [ + "audits[color-contrast].title" + ], + "core/audits/accessibility/color-contrast.js | description": [ + "audits[color-contrast].description" + ], + "core/audits/accessibility/definition-list.js | title": [ + "audits[definition-list].title" + ], + "core/audits/accessibility/definition-list.js | description": [ + "audits[definition-list].description" + ], + "core/audits/accessibility/dlitem.js | title": ["audits.dlitem.title"], + "core/audits/accessibility/dlitem.js | description": [ + "audits.dlitem.description" + ], + "core/audits/accessibility/document-title.js | title": [ + "audits[document-title].title" + ], + "core/audits/accessibility/document-title.js | description": [ + "audits[document-title].description" + ], + "core/audits/accessibility/duplicate-id-aria.js | title": [ + "audits[duplicate-id-aria].title" + ], + "core/audits/accessibility/duplicate-id-aria.js | description": [ + "audits[duplicate-id-aria].description" + ], + "core/audits/accessibility/empty-heading.js | title": [ + "audits[empty-heading].title" + ], + "core/audits/accessibility/empty-heading.js | description": [ + "audits[empty-heading].description" + ], + "core/audits/accessibility/form-field-multiple-labels.js | title": [ + "audits[form-field-multiple-labels].title" + ], + "core/audits/accessibility/form-field-multiple-labels.js | description": [ + "audits[form-field-multiple-labels].description" + ], + "core/audits/accessibility/frame-title.js | title": [ + "audits[frame-title].title" + ], + "core/audits/accessibility/frame-title.js | description": [ + "audits[frame-title].description" + ], + "core/audits/accessibility/heading-order.js | title": [ + "audits[heading-order].title" + ], + "core/audits/accessibility/heading-order.js | description": [ + "audits[heading-order].description" + ], + "core/audits/accessibility/html-has-lang.js | title": [ + "audits[html-has-lang].title" + ], + "core/audits/accessibility/html-has-lang.js | description": [ + "audits[html-has-lang].description" + ], + "core/audits/accessibility/html-lang-valid.js | title": [ + "audits[html-lang-valid].title" + ], + "core/audits/accessibility/html-lang-valid.js | description": [ + "audits[html-lang-valid].description" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | title": [ + "audits[html-xml-lang-mismatch].title" + ], + "core/audits/accessibility/html-xml-lang-mismatch.js | description": [ + "audits[html-xml-lang-mismatch].description" + ], + "core/audits/accessibility/identical-links-same-purpose.js | title": [ + "audits[identical-links-same-purpose].title" + ], + "core/audits/accessibility/identical-links-same-purpose.js | description": [ + "audits[identical-links-same-purpose].description" + ], + "core/audits/accessibility/image-alt.js | title": [ + "audits[image-alt].title" + ], + "core/audits/accessibility/image-alt.js | description": [ + "audits[image-alt].description" + ], + "core/audits/accessibility/image-redundant-alt.js | title": [ + "audits[image-redundant-alt].title" + ], + "core/audits/accessibility/image-redundant-alt.js | description": [ + "audits[image-redundant-alt].description" + ], + "core/audits/accessibility/input-button-name.js | title": [ + "audits[input-button-name].title" + ], + "core/audits/accessibility/input-button-name.js | description": [ + "audits[input-button-name].description" + ], + "core/audits/accessibility/input-image-alt.js | title": [ + "audits[input-image-alt].title" + ], + "core/audits/accessibility/input-image-alt.js | description": [ + "audits[input-image-alt].description" + ], + "core/audits/accessibility/label-content-name-mismatch.js | title": [ + "audits[label-content-name-mismatch].title" + ], + "core/audits/accessibility/label-content-name-mismatch.js | description": [ + "audits[label-content-name-mismatch].description" + ], + "core/audits/accessibility/label.js | title": ["audits.label.title"], + "core/audits/accessibility/label.js | description": [ + "audits.label.description" + ], + "core/audits/accessibility/landmark-one-main.js | title": [ + "audits[landmark-one-main].title" + ], + "core/audits/accessibility/landmark-one-main.js | description": [ + "audits[landmark-one-main].description" + ], + "core/audits/accessibility/link-name.js | title": [ + "audits[link-name].title" + ], + "core/audits/accessibility/link-name.js | description": [ + "audits[link-name].description" + ], + "core/audits/accessibility/link-in-text-block.js | title": [ + "audits[link-in-text-block].title" + ], + "core/audits/accessibility/link-in-text-block.js | description": [ + "audits[link-in-text-block].description" + ], + "core/audits/accessibility/list.js | title": ["audits.list.title"], + "core/audits/accessibility/list.js | description": [ + "audits.list.description" + ], + "core/audits/accessibility/listitem.js | title": [ + "audits.listitem.title" + ], + "core/audits/accessibility/listitem.js | description": [ + "audits.listitem.description" + ], + "core/audits/accessibility/meta-refresh.js | title": [ + "audits[meta-refresh].title" + ], + "core/audits/accessibility/meta-refresh.js | description": [ + "audits[meta-refresh].description" + ], + "core/audits/accessibility/meta-viewport.js | title": [ + "audits[meta-viewport].title" + ], + "core/audits/accessibility/meta-viewport.js | description": [ + "audits[meta-viewport].description" + ], + "core/audits/accessibility/object-alt.js | title": [ + "audits[object-alt].title" + ], + "core/audits/accessibility/object-alt.js | description": [ + "audits[object-alt].description" + ], + "core/audits/accessibility/select-name.js | title": [ + "audits[select-name].title" + ], + "core/audits/accessibility/select-name.js | description": [ + "audits[select-name].description" + ], + "core/audits/accessibility/skip-link.js | title": [ + "audits[skip-link].title" + ], + "core/audits/accessibility/skip-link.js | description": [ + "audits[skip-link].description" + ], + "core/audits/accessibility/tabindex.js | title": [ + "audits.tabindex.title" + ], + "core/audits/accessibility/tabindex.js | description": [ + "audits.tabindex.description" + ], + "core/audits/accessibility/table-duplicate-name.js | title": [ + "audits[table-duplicate-name].title" + ], + "core/audits/accessibility/table-duplicate-name.js | description": [ + "audits[table-duplicate-name].description" + ], + "core/audits/accessibility/table-fake-caption.js | title": [ + "audits[table-fake-caption].title" + ], + "core/audits/accessibility/table-fake-caption.js | description": [ + "audits[table-fake-caption].description" + ], + "core/audits/accessibility/target-size.js | title": [ + "audits[target-size].title" + ], + "core/audits/accessibility/target-size.js | description": [ + "audits[target-size].description" + ], + "core/audits/accessibility/td-has-header.js | title": [ + "audits[td-has-header].title" + ], + "core/audits/accessibility/td-has-header.js | description": [ + "audits[td-has-header].description" + ], + "core/audits/accessibility/td-headers-attr.js | title": [ + "audits[td-headers-attr].title" + ], + "core/audits/accessibility/td-headers-attr.js | description": [ + "audits[td-headers-attr].description" + ], + "core/audits/accessibility/th-has-data-cells.js | title": [ + "audits[th-has-data-cells].title" + ], + "core/audits/accessibility/th-has-data-cells.js | description": [ + "audits[th-has-data-cells].description" + ], + "core/audits/accessibility/valid-lang.js | title": [ + "audits[valid-lang].title" + ], + "core/audits/accessibility/valid-lang.js | description": [ + "audits[valid-lang].description" + ], + "core/audits/accessibility/video-caption.js | title": [ + "audits[video-caption].title" + ], + "core/audits/accessibility/video-caption.js | description": [ + "audits[video-caption].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | title": [ + "audits[uses-long-cache-ttl].title" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | description": [ + "audits[uses-long-cache-ttl].description" + ], + "core/audits/byte-efficiency/uses-long-cache-ttl.js | displayValue": [ + { + "values": { + "itemCount": 0 + }, + "path": "audits[uses-long-cache-ttl].displayValue" + } + ], + "core/lib/i18n/i18n.js | columnCacheTTL": [ + "audits[uses-long-cache-ttl].details.headings[1].label", + "audits[cache-insight].details.headings[1].label" + ], + "core/audits/byte-efficiency/total-byte-weight.js | title": [ + "audits[total-byte-weight].title" + ], + "core/audits/byte-efficiency/total-byte-weight.js | description": [ + "audits[total-byte-weight].description" + ], + "core/audits/byte-efficiency/total-byte-weight.js | displayValue": [ + { + "values": { + "totalBytes": 363395 + }, + "path": "audits[total-byte-weight].displayValue" + } + ], + "core/audits/byte-efficiency/offscreen-images.js | title": [ + "audits[offscreen-images].title" + ], + "core/audits/byte-efficiency/offscreen-images.js | description": [ + "audits[offscreen-images].description" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | title": [ + "audits[render-blocking-resources].title" + ], + "core/audits/byte-efficiency/render-blocking-resources.js | description": [ + "audits[render-blocking-resources].description" + ], + "core/lib/i18n/i18n.js | displayValueMsSavings": [ + { + "values": { + "wastedMs": 1350 + }, + "path": "audits[render-blocking-resources].displayValue" + }, + { + "values": { + "wastedMs": 1350 + }, + "path": "audits[render-blocking-insight].displayValue" + } + ], + "core/audits/byte-efficiency/unminified-css.js | title": [ + "audits[unminified-css].title" + ], + "core/audits/byte-efficiency/unminified-css.js | description": [ + "audits[unminified-css].description" + ], + "core/audits/byte-efficiency/unminified-javascript.js | title": [ + "audits[unminified-javascript].title" + ], + "core/audits/byte-efficiency/unminified-javascript.js | description": [ + "audits[unminified-javascript].description" + ], + "core/audits/byte-efficiency/unused-css-rules.js | title": [ + "audits[unused-css-rules].title" + ], + "core/audits/byte-efficiency/unused-css-rules.js | description": [ + "audits[unused-css-rules].description" + ], + "core/audits/byte-efficiency/unused-javascript.js | title": [ + "audits[unused-javascript].title" + ], + "core/audits/byte-efficiency/unused-javascript.js | description": [ + "audits[unused-javascript].description" + ], + "core/lib/i18n/i18n.js | displayValueByteSavings": [ + { + "values": { + "wastedBytes": 61425 + }, + "path": "audits[unused-javascript].displayValue" + } + ], + "core/audits/byte-efficiency/modern-image-formats.js | title": [ + "audits[modern-image-formats].title" + ], + "core/audits/byte-efficiency/modern-image-formats.js | description": [ + "audits[modern-image-formats].description" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | title": [ + "audits[uses-optimized-images].title" + ], + "core/audits/byte-efficiency/uses-optimized-images.js | description": [ + "audits[uses-optimized-images].description" + ], + "core/audits/byte-efficiency/uses-text-compression.js | title": [ + "audits[uses-text-compression].title" + ], + "core/audits/byte-efficiency/uses-text-compression.js | description": [ + "audits[uses-text-compression].description" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | title": [ + "audits[uses-responsive-images].title" + ], + "core/audits/byte-efficiency/uses-responsive-images.js | description": [ + "audits[uses-responsive-images].description" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | title": [ + "audits[efficient-animated-content].title" + ], + "core/audits/byte-efficiency/efficient-animated-content.js | description": [ + "audits[efficient-animated-content].description" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | title": [ + "audits[duplicated-javascript].title" + ], + "core/audits/byte-efficiency/duplicated-javascript.js | description": [ + "audits[duplicated-javascript].description" + ], + "core/audits/byte-efficiency/legacy-javascript.js | title": [ + "audits[legacy-javascript].title" + ], + "core/audits/byte-efficiency/legacy-javascript.js | description": [ + "audits[legacy-javascript].description" + ], + "core/audits/dobetterweb/doctype.js | title": ["audits.doctype.title"], + "core/audits/dobetterweb/doctype.js | description": [ + "audits.doctype.description" + ], + "core/audits/dobetterweb/charset.js | title": ["audits.charset.title"], + "core/audits/dobetterweb/charset.js | description": [ + "audits.charset.description" + ], + "core/audits/dobetterweb/dom-size.js | title": ["audits[dom-size].title"], + "core/audits/dobetterweb/dom-size.js | description": [ + "audits[dom-size].description" + ], + "core/audits/dobetterweb/dom-size.js | displayValue": [ + { + "values": { + "itemCount": 100 + }, + "path": "audits[dom-size].displayValue" + } + ], + "core/audits/dobetterweb/dom-size.js | columnStatistic": [ + "audits[dom-size].details.headings[0].label" + ], + "core/audits/dobetterweb/dom-size.js | columnValue": [ + "audits[dom-size].details.headings[2].label" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMElements": [ + "audits[dom-size].details.items[0].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMDepth": [ + "audits[dom-size].details.items[1].statistic" + ], + "core/audits/dobetterweb/dom-size.js | statisticDOMWidth": [ + "audits[dom-size].details.items[2].statistic" + ], + "core/audits/dobetterweb/geolocation-on-start.js | title": [ + "audits[geolocation-on-start].title" + ], + "core/audits/dobetterweb/geolocation-on-start.js | description": [ + "audits[geolocation-on-start].description" + ], + "core/audits/dobetterweb/inspector-issues.js | title": [ + "audits[inspector-issues].title" + ], + "core/audits/dobetterweb/inspector-issues.js | description": [ + "audits[inspector-issues].description" + ], + "core/audits/dobetterweb/inspector-issues.js | columnIssueType": [ + "audits[inspector-issues].details.headings[0].label" + ], + "core/audits/dobetterweb/no-document-write.js | title": [ + "audits[no-document-write].title" + ], + "core/audits/dobetterweb/no-document-write.js | description": [ + "audits[no-document-write].description" + ], + "core/audits/dobetterweb/js-libraries.js | title": [ + "audits[js-libraries].title" + ], + "core/audits/dobetterweb/js-libraries.js | description": [ + "audits[js-libraries].description" + ], + "core/audits/dobetterweb/notification-on-start.js | title": [ + "audits[notification-on-start].title" + ], + "core/audits/dobetterweb/notification-on-start.js | description": [ + "audits[notification-on-start].description" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | title": [ + "audits[paste-preventing-inputs].title" + ], + "core/audits/dobetterweb/paste-preventing-inputs.js | description": [ + "audits[paste-preventing-inputs].description" + ], + "core/audits/dobetterweb/uses-http2.js | title": [ + "audits[uses-http2].title" + ], + "core/audits/dobetterweb/uses-http2.js | description": [ + "audits[uses-http2].description" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | title": [ + "audits[uses-passive-event-listeners].title" + ], + "core/audits/dobetterweb/uses-passive-event-listeners.js | description": [ + "audits[uses-passive-event-listeners].description" + ], + "core/audits/seo/meta-description.js | title": [ + "audits[meta-description].title" + ], + "core/audits/seo/meta-description.js | description": [ + "audits[meta-description].description" + ], + "core/audits/seo/http-status-code.js | title": [ + "audits[http-status-code].title" + ], + "core/audits/seo/http-status-code.js | description": [ + "audits[http-status-code].description" + ], + "core/audits/seo/font-size.js | title": ["audits[font-size].title"], + "core/audits/seo/font-size.js | description": [ + "audits[font-size].description" + ], + "core/audits/seo/font-size.js | displayValue": [ + { + "values": { + "decimalProportion": 1 + }, + "path": "audits[font-size].displayValue" + } + ], + "core/audits/seo/font-size.js | columnSelector": [ + "audits[font-size].details.headings[1].label" + ], + "core/audits/seo/font-size.js | columnPercentPageText": [ + "audits[font-size].details.headings[2].label" + ], + "core/audits/seo/font-size.js | columnFontSize": [ + "audits[font-size].details.headings[3].label" + ], + "core/audits/seo/font-size.js | legibleText": [ + "audits[font-size].details.items[0].source.value" + ], + "core/audits/seo/link-text.js | title": ["audits[link-text].title"], + "core/audits/seo/link-text.js | description": [ + "audits[link-text].description" + ], + "core/audits/seo/crawlable-anchors.js | title": [ + "audits[crawlable-anchors].title" + ], + "core/audits/seo/crawlable-anchors.js | description": [ + "audits[crawlable-anchors].description" + ], + "core/audits/seo/crawlable-anchors.js | columnFailingLink": [ + "audits[crawlable-anchors].details.headings[0].label" + ], + "core/audits/seo/is-crawlable.js | title": ["audits[is-crawlable].title"], + "core/audits/seo/is-crawlable.js | description": [ + "audits[is-crawlable].description" + ], + "core/audits/seo/robots-txt.js | failureTitle": [ + "audits[robots-txt].title" + ], + "core/audits/seo/robots-txt.js | description": [ + "audits[robots-txt].description" + ], + "core/audits/seo/robots-txt.js | displayValueValidationError": [ + { + "values": { + "itemCount": 48 + }, + "path": "audits[robots-txt].displayValue" + } + ], + "core/audits/seo/hreflang.js | title": ["audits.hreflang.title"], + "core/audits/seo/hreflang.js | description": [ + "audits.hreflang.description" + ], + "core/audits/seo/canonical.js | title": ["audits.canonical.title"], + "core/audits/seo/canonical.js | description": [ + "audits.canonical.description" + ], + "core/audits/seo/manual/structured-data.js | title": [ + "audits[structured-data].title" + ], + "core/audits/seo/manual/structured-data.js | description": [ + "audits[structured-data].description" + ], + "core/audits/bf-cache.js | title": ["audits[bf-cache].title"], + "core/audits/bf-cache.js | description": ["audits[bf-cache].description"], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | title": [ + "audits[cache-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | description": [ + "audits[cache-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Cache.js | requestColumn": [ + "audits[cache-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | title": [ + "audits[cls-culprits-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/CLSCulprits.js | description": [ + "audits[cls-culprits-insight].description" + ], + "core/audits/insights/cls-culprits-insight.js | columnScore": [ + "audits[cls-culprits-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | title": [ + "audits[document-latency-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | description": [ + "audits[document-latency-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingRedirects": [ + "audits[document-latency-insight].details.items.noRedirects.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingServerResponseTime": [ + { + "values": { + "PH1": "2 ms" + }, + "path": "audits[document-latency-insight].details.items.serverResponseIsFast.label" + } + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DocumentLatency.js | passingTextCompression": [ + "audits[document-latency-insight].details.items.usesCompression.label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | title": [ + "audits[dom-size-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | description": [ + "audits[dom-size-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | statistic": [ + "audits[dom-size-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | value": [ + "audits[dom-size-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | totalElements": [ + "audits[dom-size-insight].details.items[0].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxChildren": [ + "audits[dom-size-insight].details.items[1].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DOMSize.js | maxDOMDepth": [ + "audits[dom-size-insight].details.items[2].statistic" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | title": [ + "audits[duplicated-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | description": [ + "audits[duplicated-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnSource": [ + "audits[duplicated-javascript-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/DuplicatedJavaScript.js | columnDuplicatedBytes": [ + "audits[duplicated-javascript-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | title": [ + "audits[font-display-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/FontDisplay.js | description": [ + "audits[font-display-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | title": [ + "audits[forced-reflow-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | description": [ + "audits[forced-reflow-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ForcedReflow.js | totalReflowTime": [ + "audits[forced-reflow-insight].details.items[0].headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | title": [ + "audits[image-delivery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ImageDelivery.js | description": [ + "audits[image-delivery-insight].description" + ], + "core/lib/i18n/i18n.js | columnResourceSize": [ + "audits[image-delivery-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | title": [ + "audits[inp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/INPBreakdown.js | description": [ + "audits[inp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | title": [ + "audits[lcp-breakdown-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | description": [ + "audits[lcp-breakdown-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | subpart": [ + "audits[lcp-breakdown-insight].details.items[0].headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | timeToFirstByte": [ + "audits[lcp-breakdown-insight].details.items[0].items[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPBreakdown.js | elementRenderDelay": [ + "audits[lcp-breakdown-insight].details.items[0].items[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | title": [ + "audits[lcp-discovery-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LCPDiscovery.js | description": [ + "audits[lcp-discovery-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | title": [ + "audits[legacy-javascript-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | description": [ + "audits[legacy-javascript-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/LegacyJavaScript.js | columnWastedBytes": [ + "audits[legacy-javascript-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | title": [ + "audits[modern-http-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | description": [ + "audits[modern-http-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ModernHTTP.js | protocol": [ + "audits[modern-http-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | title": [ + "audits[network-dependency-tree-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | description": [ + "audits[network-dependency-tree-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableTitle": [ + "audits[network-dependency-tree-insight].details.items[1].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | preconnectOriginsTableDescription": [ + "audits[network-dependency-tree-insight].details.items[1].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectOrigins": [ + "audits[network-dependency-tree-insight].details.items[1].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableTitle": [ + "audits[network-dependency-tree-insight].details.items[2].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | estSavingTableDescription": [ + "audits[network-dependency-tree-insight].details.items[2].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/NetworkDependencyTree.js | noPreconnectCandidates": [ + "audits[network-dependency-tree-insight].details.items[2].value.value" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | title": [ + "audits[render-blocking-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/RenderBlocking.js | description": [ + "audits[render-blocking-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | title": [ + "audits[third-parties-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | description": [ + "audits[third-parties-insight].description" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnThirdParty": [ + "audits[third-parties-insight].details.headings[0].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnTransferSize": [ + "audits[third-parties-insight].details.headings[1].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/ThirdParties.js | columnMainThreadTime": [ + "audits[third-parties-insight].details.headings[2].label" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | title": [ + "audits[viewport-insight].title" + ], + "node_modules/@paulirish/trace_engine/models/trace/insights/Viewport.js | description": [ + "audits[viewport-insight].description" + ], + "core/config/default-config.js | performanceCategoryTitle": [ + "categories.performance.title" + ], + "core/config/default-config.js | a11yCategoryTitle": [ + "categories.accessibility.title" + ], + "core/config/default-config.js | a11yCategoryDescription": [ + "categories.accessibility.description" + ], + "core/config/default-config.js | a11yCategoryManualDescription": [ + "categories.accessibility.manualDescription" + ], + "core/config/default-config.js | bestPracticesCategoryTitle": [ + "categories[best-practices].title" + ], + "core/config/default-config.js | seoCategoryTitle": [ + "categories.seo.title" + ], + "core/config/default-config.js | seoCategoryDescription": [ + "categories.seo.description" + ], + "core/config/default-config.js | seoCategoryManualDescription": [ + "categories.seo.manualDescription" + ], + "core/config/default-config.js | metricGroupTitle": [ + "categoryGroups.metrics.title" + ], + "core/config/default-config.js | insightsGroupTitle": [ + "categoryGroups.insights.title" + ], + "core/config/default-config.js | insightsGroupDescription": [ + "categoryGroups.insights.description" + ], + "core/config/default-config.js | diagnosticsGroupTitle": [ + "categoryGroups.diagnostics.title" + ], + "core/config/default-config.js | diagnosticsGroupDescription": [ + "categoryGroups.diagnostics.description" + ], + "core/config/default-config.js | a11yBestPracticesGroupTitle": [ + "categoryGroups[a11y-best-practices].title" + ], + "core/config/default-config.js | a11yBestPracticesGroupDescription": [ + "categoryGroups[a11y-best-practices].description" + ], + "core/config/default-config.js | a11yColorContrastGroupTitle": [ + "categoryGroups[a11y-color-contrast].title" + ], + "core/config/default-config.js | a11yColorContrastGroupDescription": [ + "categoryGroups[a11y-color-contrast].description" + ], + "core/config/default-config.js | a11yNamesLabelsGroupTitle": [ + "categoryGroups[a11y-names-labels].title" + ], + "core/config/default-config.js | a11yNamesLabelsGroupDescription": [ + "categoryGroups[a11y-names-labels].description" + ], + "core/config/default-config.js | a11yNavigationGroupTitle": [ + "categoryGroups[a11y-navigation].title" + ], + "core/config/default-config.js | a11yNavigationGroupDescription": [ + "categoryGroups[a11y-navigation].description" + ], + "core/config/default-config.js | a11yAriaGroupTitle": [ + "categoryGroups[a11y-aria].title" + ], + "core/config/default-config.js | a11yAriaGroupDescription": [ + "categoryGroups[a11y-aria].description" + ], + "core/config/default-config.js | a11yLanguageGroupTitle": [ + "categoryGroups[a11y-language].title" + ], + "core/config/default-config.js | a11yLanguageGroupDescription": [ + "categoryGroups[a11y-language].description" + ], + "core/config/default-config.js | a11yAudioVideoGroupTitle": [ + "categoryGroups[a11y-audio-video].title" + ], + "core/config/default-config.js | a11yAudioVideoGroupDescription": [ + "categoryGroups[a11y-audio-video].description" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupTitle": [ + "categoryGroups[a11y-tables-lists].title" + ], + "core/config/default-config.js | a11yTablesListsVideoGroupDescription": [ + "categoryGroups[a11y-tables-lists].description" + ], + "core/config/default-config.js | seoMobileGroupTitle": [ + "categoryGroups[seo-mobile].title" + ], + "core/config/default-config.js | seoMobileGroupDescription": [ + "categoryGroups[seo-mobile].description" + ], + "core/config/default-config.js | seoContentGroupTitle": [ + "categoryGroups[seo-content].title" + ], + "core/config/default-config.js | seoContentGroupDescription": [ + "categoryGroups[seo-content].description" + ], + "core/config/default-config.js | seoCrawlingGroupTitle": [ + "categoryGroups[seo-crawl].title" + ], + "core/config/default-config.js | seoCrawlingGroupDescription": [ + "categoryGroups[seo-crawl].description" + ], + "core/config/default-config.js | bestPracticesTrustSafetyGroupTitle": [ + "categoryGroups[best-practices-trust-safety].title" + ], + "core/config/default-config.js | bestPracticesUXGroupTitle": [ + "categoryGroups[best-practices-ux].title" + ], + "core/config/default-config.js | bestPracticesBrowserCompatGroupTitle": [ + "categoryGroups[best-practices-browser-compat].title" + ], + "core/config/default-config.js | bestPracticesGeneralGroupTitle": [ + "categoryGroups[best-practices-general].title" + ] + } + } +} diff --git a/docs/planning/adrs/ADR-015-supabase-evaluation-deferred.md b/docs/planning/adrs/ADR-015-supabase-evaluation-deferred.md new file mode 100644 index 00000000..c2aa9dfb --- /dev/null +++ b/docs/planning/adrs/ADR-015-supabase-evaluation-deferred.md @@ -0,0 +1,83 @@ +# ADR-015: Avaliação de Supabase adiada para Sprint 6 (gatilho explícito) + +## Status + +**Proposed — Deferred** (registro de decisão de não-fazer agora; revisitar em Sprint 6+ se gatilho atingido). + +## Context + +Em 2026-06-09, durante encerramento da Sprint 4 (US30.1 busca editorial), foi levantada a possibilidade de adotar **Supabase + Cloudflare + Hostinger** como stack. Análise honesta: + +- **Cloudflare** já é parte da stack ([ADR-003](ADR-003-cloudflare-pos-dominio.md)) +- **Hostinger KVM 1** já é parte da stack ([ADR-005](ADR-005-hostinger-kvm1.md)) +- **Supabase** seria adição nova — nenhuma menção em 4.155 LOC de planning, 14 ADRs project-wide, 35 ADRs da busca, HOSTING-DEPLOY-PLAN, ou Improvement-system + +Existem **3 cenários distintos** para Supabase, com custos de retrabalho muito diferentes: + +| Cenário | Impacto operacional | Impacto na spec existente | +|---|---|---| +| **(A) Suplemento** — Storage (capas), futuro pgvector, futuro Edge Functions | Postgres fica no Hostinger; Django intacto | Nenhum no que existe; adicionamos ADRs novos | +| **(B) DB managed** — substitui Postgres self-hosted por Supabase Postgres | Reescrever migrations 0001-0005 da busca; CONFIGURATION pt_unaccent + role custom + statement_timeout + trigger ENABLE ALWAYS exigem **Supabase Pro tier** ($25/mês) ou workaround complexo | Quebra ADRs 018/019/021b da busca | +| **(C) Replatform** — substitui Django por PostgREST + Auth + RLS + Edge Functions | Joga fora ~60 commits da feature US30.1; refazer auth (Django roles → Supabase RLS), admin (Django admin → ???), Celery (→ pg_cron + Edge) | Invalida ADRs 001-014 + boa parte das 35 ADRs da busca | + +## Decision + +**Adiar avaliação para Sprint 6, cenário (A) apenas.** Não considerar cenários (B) ou (C) até que haja razão concreta de produto (não apenas tech-curiosity). + +### Gatilhos para entrar em Sprint 6 com este spike + +Pelo menos **um** dos seguintes: + +1. **Disco do KVM 1 ≥ 70% usado** — capas de artigos + media saturando o storage local +2. **Demanda concreta por semantic search** — leitor pede "artigos parecidos" ou time editorial pede recommendation engine; **pgvector** é convidativo +3. **Demanda concreta por real-time** — comments live, notificações push, "leitores ativos agora" +4. **Sprint 5 fechado** + janela disponível para spike técnico de descoberta + +### Escopo do Sprint 6 quando entrar + +Timeboxed em 3 dias: + +1. **POC Supabase Storage** — subir 1 capa, servir via CDN, comparar latência vs nginx local +2. **Análise de custo pgvector** — esforço de integração vs ganho percebido +3. **Análise de custo Edge Functions** — quais endpoints valeriam migração + +### Não-objetivos + +- Migrar Postgres atual (cenário B) +- Migrar Django auth (cenário B/C) +- Refazer admin (cenário C) + +## Consequences + +### Positivas + +- Preserva 60 commits da US30.1 e PR #37 mergeado em main como `2bdf73b` +- Não invalida 35 ADRs da busca (incluindo ADR-018 trigger SQL + ADR-019 CONFIGURATION pt_unaccent + ADR-021b mitigações GIN custom) +- Mantém previsibilidade de custo (KVM 1 ~R$ 40/mês vs Supabase Pro $25/mês + bandwidth) +- Decisão é **registrada com gatilho explícito** (não esquecida) + +### Negativas + +- Se um dos gatilhos disparar abruptamente (ex.: disco saturado em prod), entrada no Sprint 6 será reativa, não planejada +- Storage local em nginx tem limite prático (~30k MAU = ~5GB de capas) — operacionalmente precisa ser monitorado em runbook (`disk-full.md`) + +### Trade-offs aceitos + +- Sem managed backups Supabase — continuamos com `pg_dump` cron para B2 (ADR-032 da busca) +- Sem managed auth — continuamos com Django + JWT em cookie + django-axes +- Sem managed real-time — sem comments live no MVP (aceitável; produto editorial não é chat) + +## Cross-ref + +- Sprint planejado: [`docs/backlog/sprints/sprint-6-supabase-evaluation.md`](../../backlog/sprints/sprint-6-supabase-evaluation.md) +- ADR-003 Cloudflare (premissa preservada): [`ADR-003`](ADR-003-cloudflare-pos-dominio.md) +- ADR-005 Hostinger KVM 1 (premissa preservada): [`ADR-005`](ADR-005-hostinger-kvm1.md) +- ADR-018 da busca (incompatível com cenário B): [`docs/specs/busca-editorial/adrs/ADR-018-trigger-sql-fonte-verdade-consistencia.md`](../../specs/busca-editorial/adrs/ADR-018-trigger-sql-fonte-verdade-consistencia.md) +- ADR-019 da busca (incompatível com cenário B): [`docs/specs/busca-editorial/adrs/ADR-019-fts-pt-unaccent-configuration.md`](../../specs/busca-editorial/adrs/ADR-019-fts-pt-unaccent-configuration.md) + +## Histórico + +| Data | Evento | +|---|---| +| 2026-06-09 | Decisão de adiar para Sprint 6 com gatilhos. Registrada após análise de impacto dos 3 cenários. Anti-sycophancy aplicada: recusa de adotar Supabase agora sem razão de produto. | +| TBD | Revisitar quando gatilho disparar | diff --git a/docs/requirements/README.md b/docs/requirements/README.md new file mode 100644 index 00000000..76019392 --- /dev/null +++ b/docs/requirements/README.md @@ -0,0 +1,88 @@ +# Requisitos — Interpop + +> **Pasta-fonte do "o QUÊ" do produto.** Tudo aqui responde "que necessidade o sistema atende?", sem entrar em "como" (isso é `architecture/` + `specs/` + ADRs). + +## Hierarquia de rastreabilidade (engenharia-de-requisitos) + +``` +Requisito (RF/RNF) ← ESTA pasta + ↓ realizado por +Epic ← docs/backlog/epics/ + ↓ decomposto em +Feature ← docs/backlog/features/ + ↓ aceito quando +Critério de Aceitação (CA) ← dentro do arquivo de Feature + ↓ ilustrado por +User Story (US) + cenários BDD ← dentro do arquivo de Feature + ↓ implementado por +Task (T / TX) ← dentro do arquivo de Feature + ↓ entregue em +Sprint ← docs/backlog/sprints/ + ↓ materializada em +Commit (SHA) +``` + +**Regra dura**: cada nível cita explicitamente os nós **pai** e **filhos** via link relativo. Sem rastreabilidade bidirecional, a documentação vira ficção — quando alguém ajustar um requisito, precisa enxergar imediatamente quais Epics/Features/CAs/Tasks são afetados. + +## Estrutura + +``` +requirements/ +├── README.md este arquivo +├── personas-e-cenarios.md personas (anônimo, autenticado, editor, admin, dev) + casos de uso +├── RF/ Requisitos Funcionais (1 arquivo por módulo) +│ ├── RF-001-articles.md +│ ├── RF-002-comments.md +│ ├── RF-003-moderation.md +│ ├── RF-004-newsletter.md +│ ├── RF-005-users-auth.md +│ ├── RF-006-audit.md +│ └── RF-007-busca-editorial.md +└── RNF/ Requisitos Não-Funcionais (corte transversal) + ├── RNF-perf.md + ├── RNF-security.md + ├── RNF-a11y.md + ├── RNF-lgpd.md + └── RNF-availability.md +``` + +## Convenções (idênticas às de `backlog/README.md` — Interpop/IFPB) + +- **pt-BR explícito**. Nunca infinitivo no título de RF. Use verbo no presente do indicativo: "Sistema permite que leitor busque artigos por termo livre" (não "permitir busca"). +- **Sem termo técnico no título do RF**. Linguagem de negócio. `tsvector`, `JWT`, `Postgres` ficam na seção "decisões técnicas relacionadas" ou nas ADRs — não no enunciado do requisito. +- **IDs canônicos**: `RF-NNN`, `RNF-NN`. Imutáveis depois de criados. Se requisito for descontinuado, vira `RF-NNN-deprecated.md` (não some). +- **Prioridade**: 🔴 Imediato (MVP/segurança) · 🟠 Alta (release atual) · 🟡 Normal (próxima sprint) · ⚪ Baixa (backlog longo). +- **Cada arquivo tem seção `## Realizado por`**: lista de Epics/Features que executam este requisito. + +## Como adicionar um requisito novo + +1. Identifique o módulo: existe `RF-NNN` correspondente? Se sim, adicione uma seção. Se não, crie `RF-NNN-novo-modulo.md` com próximo número livre. +2. Escreva o enunciado em pt-BR de negócio (sem jargão técnico). +3. Marque prioridade explícita. +4. Adicione seção `## Realizado por` listando Epic(s) já aprovados que executam este requisito (vazio se ainda não há). +5. Quando um Epic novo for criado citando este requisito, **edite este arquivo** para atualizar `## Realizado por` — rastreabilidade bidirecional é obrigatória. + +## Link com a skill canônica + +Tudo aqui segue [`~/.claude/skills/engenharia-de-requisitos/`](https://github.com/seekdevcore/sk-requirements-engineering-theskill) (baseada em curso ERS IFPB + Sommerville 10e + Pressman 9e + Wiegers 3e + Cohn + Robertson + BABOK v3). + +A skill exige: + +1. Hierarquia Epic → Feature → CA · US → BDD → Task +2. Naming pt-BR (sem infinitivo, sem termo técnico em níveis de negócio) +3. Cross-references bidirecionais +4. Critérios de Aceitação testáveis em booleano +5. Cenários BDD em Gherkin pt-BR +6. Definition of Done explícita + +## Cross-references rápidas + +- [Backlog operacional](../backlog/README.md) — Epics, Features, Sprints +- [Specs técnicas](../specs/) — DESIGN, ADRs por feature +- [ADRs do projeto](../planning/adrs/) — decisões arquiteturais transversais +- [Architecture overview](../architecture/overview.md) — visão de cima +- [Testing standards](../tests/testing-standards.md) — política de testes + +--- + +_Criado em 2026-06-09 como parte da reorganização `chore/docs-reorg`._ diff --git a/docs/requirements/RF/RF-001-articles.md b/docs/requirements/RF/RF-001-articles.md new file mode 100644 index 00000000..4cce1d62 --- /dev/null +++ b/docs/requirements/RF/RF-001-articles.md @@ -0,0 +1,119 @@ +# RF-001 — Publicação e leitura de artigos editoriais + +> **Tipo**: Requisito Funcional +> **Prioridade**: 🔴 Imediato (núcleo editorial — sem isto não há produto) +> **Status**: ✅ Realizado em produção (Sprint 1-2, pre-busca) · 🚧 documentação retroativa formalizada nesta entrega + +--- + +## Enunciado de negócio (pt-BR, sem termo técnico) + +> **Sistema permite que editor publique artigos editoriais com título, resumo, corpo de texto, capa, autor e editoria, e que leitor anônimo ou autenticado leia esses artigos via URL com slug humano, com contagem de visualização anti-abuso e meta tags ricas para crawlers sociais.** + +### Subseção §publicação + +> Editor (papel `editor`, `admin` ou `dev`) cria artigo em rascunho, preenche título, resumo, corpo de texto, imagem de capa com legenda obrigatória e escolhe uma das 5 editorias canônicas. Quando publica, o sistema registra automaticamente o momento da publicação e notifica assinantes de newsletter por e-mail. + +### Subseção §leitura + +> Qualquer leitor (anônimo ou autenticado) acessa o artigo via URL pública no formato `/noticia/`. O slug aceita acentuação portuguesa (`/noticia/o-novo-disco-da-céu`). Leitor anônimo nunca vê artigo em rascunho; editorial autenticado vê rascunhos seus na listagem administrativa. + +### Subseção §view_count + +> Cada leitura conta como uma visualização, mas o mesmo leitor (mesmo IP) só conta uma vez a cada 5 minutos por artigo. Isso impede inflação artificial por F5 ou recarga acidental. A contagem é usada para ranquear "mais lidos" e nunca decresce. + +### Subseção §OG-meta + +> Quando alguém compartilha um link de artigo em WhatsApp, Twitter, Facebook, LinkedIn, Telegram, Discord, Slack ou Pinterest, a prévia mostrada (cartão social) traz título, resumo, capa e autor corretos. O sistema detecta o robô do aplicativo de mensagem e responde com a página enriquecida em meta tags, sem depender do JavaScript do navegador. + +### Subseção §categorias-fixas + +> Sistema oferece exatamente 5 editorias canônicas (Música, Moda, Cinema, Literatura, Cultura Digital). Editor escolhe uma por artigo. Vocabulário é estável — adicionar nova editoria é decisão editorial deliberada, não criação livre por editor (ver Restrição "tags livres deferidas"). + +--- + +## Justificativa (por que este requisito existe) + +Interpop é um veículo editorial brasileiro de análise crítica de Soft Power e geopolítica da cultura pop. Publicação editorial **é o produto** — sem ela, não há leitor, não há retenção, não há newsletter, não há comentário, não há busca. Todos os demais módulos (`comments`, `newsletter`, `search`, `audit`, `moderation`) lêem ou referenciam `Article`. + +Por que cada subseção existe: + +- **§publicação**: editor precisa de fluxo de rascunho → publicado para permitir revisão antes do leitor ver. Sem isso, todo erro de digitação vira erro público. +- **§leitura**: URL humana (`/noticia/a-nova-hegemonia-coreana`) é melhor para SEO, compartilhamento e memorização do que `/articles/uuid/`. Acentuação portuguesa importa porque o público é brasileiro e títulos editoriais brasileiros têm acento. +- **§view_count**: KPI editorial mais usado pelo time é "mais lidos da semana". Contagem inflada por F5 do próprio autor invalida o ranking. +- **§OG-meta**: 60-80% do tráfego de artigos editoriais brasileiros vem de redes sociais (Twitter/X + WhatsApp). Cartão social pobre = cliques perdidos. +- **§categorias-fixas**: editorias são identidade de marca. Tag livre cria caos taxonômico (sinônimos, plurais, capitalização) e dilui SEO. + +**Implicação de produto**: este RF é a **fundação editorial** — qualquer Sprint que toque artigos deve preservar todos os 5 sub-comportamentos sem regressão. + +--- + +## Realizado por (rastreabilidade ↓) + +Este requisito é executado pelos seguintes Epics e Features: + +| Epic | Feature(s) | Status | +| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------- | +| [EP-02 Publicação editorial](../../backlog/epics/EP-02-publicacao-editorial.md) | [F-10 Publicação e leitura de artigos](../../backlog/features/F-10-publicacao-leitura-artigos.md) | ✅ Done (Sprint 1-2, pre-busca) | + +--- + +## Requisitos Não-Funcionais que limitam este RF + +| RNF | Limite imposto | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [RNF-perf](../RNF/RNF-perf.md) | Listing público p95 ≤ 200ms server (cobre índice `(status, -published_at)` em `articles`); LCP/INP/CLS dentro dos gates | +| [RNF-security](../RNF/RNF-security.md) | `IsPublisherOrReadOnly` + `IsOwnerOrAdmin` (editor X não toca artigo de editor Y); escape XSS no boundary do `body` (defesa em camada única documentada — débito S-01) | +| [RNF-a11y](../RNF/RNF-a11y.md) | Página de artigo passa WCAG 2.2 AA; landmarks corretos (`
          `, `
          `, `

        + )} + ) : (

        Seja o primeiro a comentar.

        )} diff --git a/src/components/article/__tests__/ArticleComments.test.tsx b/src/components/article/__tests__/ArticleComments.test.tsx new file mode 100644 index 00000000..2bd92816 --- /dev/null +++ b/src/components/article/__tests__/ArticleComments.test.tsx @@ -0,0 +1,140 @@ +/** + * Testes do fluxo de paginação ("Ver mais comentários") de ArticleComments. + * + * Regressão: a API serve 20 comentários de topo por página, mas o componente + * só carregava a 1ª página — o resto ficava inacessível. Estes testes cobrem: + * - botão "Ver mais" aparece só quando há próxima página (campo `next`); + * - clicar carrega a página seguinte e ANEXA os novos itens; + * - dedup por id: publicar um comentário desloca o offset do servidor, então + * a página seguinte pode repetir um item já em tela — não pode duplicar; + * - botão some quando `next` é null (fim da lista). + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { ArticleComments } from '../ArticleComments'; +import type { ApiComment } from '@/services/commentService'; +import commentService from '@/services/commentService'; + +vi.mock('@/services/commentService', () => ({ + default: { + list: vi.fn(), + add: vi.fn(), + remove: vi.fn(), + toggleLike: vi.fn(), + }, +})); + +// Stub do CommentItem: evita arrastar suas deps (auth, like service). Só +// precisamos enxergar o conteúdo renderizado para asserções. +vi.mock('../../ui/CommentItem', () => ({ + CommentItem: ({ comment }: { comment: ApiComment }) => ( +
      • {comment.content}
      • + ), +})); + +function makeComment(id: string, content: string): ApiComment { + return { + id, + author: { id: 'u1', full_name: 'Autor', avatar: null, avatar_initial: 'A' }, + content, + parent_id: null, + created_at: '2026-05-28T00:00:00Z', + likes_count: 0, + is_liked: false, + replies_count: 0, + replies: [], + }; +} + +const mockedList = vi.mocked(commentService.list); + +function renderComments() { + return render( + + + , + ); +} + +describe('ArticleComments — paginação', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('mostra "Ver mais" quando a API indica próxima página', async () => { + mockedList.mockResolvedValueOnce({ + data: { + count: 3, + next: 'http://x/?page=2', + previous: null, + results: [makeComment('a', 'comentário A')], + }, + } as Awaited>); + + renderComments(); + + expect(await screen.findByText('comentário A')).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: /ver mais coment/i }), + ).toBeInTheDocument(); + }); + + it('não mostra "Ver mais" quando next é null', async () => { + mockedList.mockResolvedValueOnce({ + data: { + count: 1, + next: null, + previous: null, + results: [makeComment('a', 'único')], + }, + } as Awaited>); + + renderComments(); + + expect(await screen.findByText('único')).toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: /ver mais coment/i }), + ).not.toBeInTheDocument(); + }); + + it('clicar em "Ver mais" anexa a próxima página com dedup por id', async () => { + mockedList + .mockResolvedValueOnce({ + data: { + count: 3, + next: 'http://x/?page=2', + previous: null, + results: [makeComment('a', 'A'), makeComment('b', 'B')], + }, + } as Awaited>) + .mockResolvedValueOnce({ + data: { + count: 3, + next: null, + previous: 'http://x/?page=1', + // 'B' repetido (offset deslocado) + 'C' novo + results: [makeComment('b', 'B'), makeComment('c', 'C')], + }, + } as Awaited>); + + renderComments(); + + await screen.findByText('A'); + fireEvent.click(screen.getByRole('button', { name: /ver mais coment/i })); + + // 'C' chega; 'B' não duplica; botão some (next=null na pág. 2) + expect(await screen.findByText('C')).toBeInTheDocument(); + expect(screen.getAllByText('B')).toHaveLength(1); + await waitFor(() => + expect( + screen.queryByRole('button', { name: /ver mais coment/i }), + ).not.toBeInTheDocument(), + ); + + // Página 2 foi pedida com page=2 + expect(mockedList).toHaveBeenNthCalledWith(2, 'artigo-teste', { + page: '2', + }); + }); +}); diff --git a/src/components/layout/AuthLayout.tsx b/src/components/layout/AuthLayout.tsx index e3c54088..537528ff 100644 --- a/src/components/layout/AuthLayout.tsx +++ b/src/components/layout/AuthLayout.tsx @@ -110,7 +110,7 @@ export function AuthLayout({ children, heading, subheading }: AuthLayoutProps) { } > - + {currentUser.first_name} {currentUser.last_name}

        + {currentUser.username && ( +

        + @{currentUser.username} +

        + )} {currentUser.email && (

        {currentUser.email}

        )} diff --git a/src/components/ui/CommentItem.css b/src/components/ui/CommentItem.css index cbcb77fe..d38c8c27 100644 --- a/src/components/ui/CommentItem.css +++ b/src/components/ui/CommentItem.css @@ -72,6 +72,15 @@ gap: 0.875rem; } +.comment-item__error { + margin: 0.5rem 0 0; + padding: 0.375rem 0.625rem; + font-size: 0.8125rem; + color: #991b1b; + background: #fee2e2; + border-radius: var(--radius-md, 0.375rem); +} + .comment-item__like { display: inline-flex; align-items: center; diff --git a/src/components/ui/CommentItem.tsx b/src/components/ui/CommentItem.tsx index d06c8e67..b6272ff5 100644 --- a/src/components/ui/CommentItem.tsx +++ b/src/components/ui/CommentItem.tsx @@ -5,6 +5,7 @@ import commentService from '@/services/commentService'; import { Avatar } from './Avatar'; import { Button } from './Button'; import { formatDateTime } from '@/utils/formatDate'; +import { extractApiError } from '@/utils/extractApiError'; import './CommentItem.css'; interface CommentItemProps { @@ -29,6 +30,9 @@ export function CommentItem({ const [replyText, setReplyText] = useState(''); const [submitting, setSubmitting] = useState(false); const [liking, setLiking] = useState(false); + // Antes os 3 handlers engoliam o erro (catch vazio): se curtir/responder/ + // excluir falhasse, o usuário clicava e NADA acontecia, sem pista do porquê. + const [actionError, setActionError] = useState(''); // `isAdmin` (vem do AuthContext) já cobre admin E dev — dev é admin++. const canDelete = @@ -37,11 +41,12 @@ export function CommentItem({ const handleLike = useCallback(async () => { if (!currentUser || liking) return; setLiking(true); + setActionError(''); try { const { data } = await commentService.toggleLike(comment.id); onLikeToggled(comment.id, data.liked, data.likes_count); } catch { - // silently ignore + setActionError('Não foi possível registrar a curtida. Tente novamente.'); } finally { setLiking(false); } @@ -50,6 +55,7 @@ export function CommentItem({ const handleReplySubmit = useCallback(async () => { if (!replyText.trim() || submitting) return; setSubmitting(true); + setActionError(''); try { const { data } = await commentService.add( articleSlug, @@ -59,8 +65,10 @@ export function CommentItem({ onReplyAdded(comment.id, data); setReplyText(''); setShowReplyForm(false); - } catch { - // silently ignore + } catch (err: unknown) { + setActionError( + extractApiError(err, 'Não foi possível enviar a resposta.'), + ); } finally { setSubmitting(false); } @@ -68,11 +76,14 @@ export function CommentItem({ const handleDelete = useCallback(async () => { if (!window.confirm('Excluir este comentário?')) return; + setActionError(''); try { await commentService.remove(comment.id); onDelete(comment.id); - } catch { - // silently ignore + } catch (err: unknown) { + setActionError( + extractApiError(err, 'Não foi possível excluir o comentário.'), + ); } }, [comment.id, onDelete]); @@ -174,6 +185,12 @@ export function CommentItem({
        )} + {actionError && ( +

        + {actionError} +

        + )} + {comment.replies && comment.replies.length > 0 && (
          {comment.replies.map((reply) => ( diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 6a74c85d..df2fd0d3 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, type ReactNode } from 'react'; +import { useEffect, useId, useRef, type ReactNode } from 'react'; import { Button } from './Button'; import './Modal.css'; @@ -24,6 +24,9 @@ export function Modal({ }: ModalProps) { const modalRef = useRef(null); const previouslyFocused = useRef(null); + // id único por instância — antes era hardcoded "modal-title", então 2 modais + // montados juntos colidiam (id duplicado + aria-labelledby ambíguo). + const titleId = useId(); // Focus management + Esc + scroll lock (C5 / WCAG 2.4.3 do Improvement-system §11.4). // - Ao abrir: memoriza foco anterior + move foco pro primeiro focusable do modal. @@ -92,7 +95,7 @@ export function Modal({ onClick={onClose} role="dialog" aria-modal="true" - aria-labelledby="modal-title" + aria-labelledby={titleId} >
          e.stopPropagation()} >
          - +

          {title}

          + {/* Link estilizado como botão (NÃO
          {/* Barra de filtros — sempre visível, nunca em modal */} diff --git a/src/pages/Admin/MetricsDashboard.tsx b/src/pages/Admin/MetricsDashboard.tsx index 73c281ba..93134742 100644 --- a/src/pages/Admin/MetricsDashboard.tsx +++ b/src/pages/Admin/MetricsDashboard.tsx @@ -270,12 +270,13 @@ export function MetricsDashboard({ metrics }: MetricsDashboardProps) { )} -
          -

          {totalArticles}

          -

          +

          + {/* dt=rótulo, dd=valor (evita "possible heading" no número central). */} +
          publicaç{totalArticles === 1 ? 'ão' : 'ões'} -

          -
          + +
          {totalArticles}
          +

      {visibleCategories.length > 0 && (
        diff --git a/src/pages/Admin/_metrics/HeroKpi.tsx b/src/pages/Admin/_metrics/HeroKpi.tsx index 1098fc47..e35a5da4 100644 --- a/src/pages/Admin/_metrics/HeroKpi.tsx +++ b/src/pages/Admin/_metrics/HeroKpi.tsx @@ -25,8 +25,11 @@ export function HeroKpi({ label, value, delta, deltaSuffix }: HeroKpiProps) { return (
        -

        {label}

        -

        {formatNumber(value)}

        + {/* dl/dt/dd: par rótulo→valor (WAVE não marca dt/dd como "possible heading"). */} +
        +
        {label}
        +
        {formatNumber(value)}
        +
        {delta !== null ? ( diff --git a/src/pages/Admin/_metrics/SmallStat.tsx b/src/pages/Admin/_metrics/SmallStat.tsx index 381f612c..ad9a56ab 100644 --- a/src/pages/Admin/_metrics/SmallStat.tsx +++ b/src/pages/Admin/_metrics/SmallStat.tsx @@ -12,9 +12,10 @@ interface SmallStatProps { export function SmallStat({ label, value }: SmallStatProps) { return ( -
        -

        {formatNumber(value)}

        -

        {label}

        -
        +
        + {/* dt=rótulo, dd=valor (evita o alerta "possible heading" do WAVE). */} +
        {label}
        +
        {formatNumber(value)}
        +
        ); } diff --git a/src/pages/Admin/index.tsx b/src/pages/Admin/index.tsx index ce3216ad..a7a20921 100644 --- a/src/pages/Admin/index.tsx +++ b/src/pages/Admin/index.tsx @@ -469,10 +469,13 @@ export function Admin() { -
        -

        {s.value}

        -

        {s.label}

        -
        + {/* dl/dt/dd: par rótulo→valor é semanticamente uma definição, + e o WAVE não marca dt/dd como "possible heading" (que era o + alerta nos números grandes). column-reverse mantém o valor no topo. */} +
        +
        {s.label}
        +
        {s.value}
        +
        ))}
        @@ -566,7 +569,7 @@ export function Admin() { : user.role === 'admin' ? '🛡️ Admin' : user.role === 'editor' - ? '✍️ Redator' + ? '✍️ Editor' : '📖 Leitor'} @@ -664,7 +667,7 @@ export function Admin() { color: 'var(--clr-muted)', }} > - Redatores podem solicitar banimentos de usuários — aparecem + Editores podem solicitar banimentos de usuários — aparecem aqui para sua aprovação.

    diff --git a/src/pages/Article.css b/src/pages/Article.css index fb535ea9..7d98bb46 100644 --- a/src/pages/Article.css +++ b/src/pages/Article.css @@ -340,6 +340,14 @@ font-weight: 700; margin-bottom: 2px; } +/* @handle logo abaixo do nome exibido: navy da marca (alto contraste, ~13:1), + um pouco menor que o nome e separado da role. */ +.article-author-card__username { + font-size: var(--text-sm); + color: var(--clr-primary); + font-weight: 600; + margin-bottom: 2px; +} .article-author-card__role { font-size: var(--text-xs); color: var(--clr-primary); @@ -533,3 +541,9 @@ padding: 0; margin: var(--sp-6, 1.5rem) 0 0; } + +.article-comments__more { + display: flex; + justify-content: center; + margin-top: var(--sp-5, 1.25rem); +} diff --git a/src/pages/Article.tsx b/src/pages/Article.tsx index eac45d34..7470005f 100644 --- a/src/pages/Article.tsx +++ b/src/pages/Article.tsx @@ -151,7 +151,11 @@ export function Article() { @@ -237,6 +241,11 @@ export function Article() {

    {article.author.full_name}

    + {article.author.username && ( +

    + @{article.author.username} +

    + )}

    {article.author.role === 'admin' || article.author.role === 'dev' diff --git a/src/pages/Auth/Auth.css b/src/pages/Auth/Auth.css index 7e976a28..02df666b 100644 --- a/src/pages/Auth/Auth.css +++ b/src/pages/Auth/Auth.css @@ -104,3 +104,15 @@ color: var(--clr-muted); font-size: var(--text-sm); } + +/* Mensagem de erro de auth (login/cadastro). Antes só existia em Admin.css, + que não é carregado nas telas de auth — o erro do Login renderizava sem + estilo. Definida aqui para Login + Register. */ +.auth-error { + background: var(--clr-error-soft, #fff5f5); + border: 1px solid #fca5a5; + border-radius: var(--radius-md); + padding: var(--sp-3) var(--sp-4); + color: #b91c1c; + font-size: var(--text-sm); +} diff --git a/src/pages/Auth/Register.tsx b/src/pages/Auth/Register.tsx index c89e1132..a36ce081 100644 --- a/src/pages/Auth/Register.tsx +++ b/src/pages/Auth/Register.tsx @@ -5,11 +5,17 @@ import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; import { LegalContent } from '@/pages/Legal/LegalContent'; +import { useAuth } from '@/contexts/AuthContext'; +import { authService } from '@/services/authService'; +import { extractApiError } from '@/utils/extractApiError'; +import { PasswordChecklist } from '@/components/ui/PasswordChecklist'; +import { isPasswordStrong } from '@/utils/passwordRules'; import './Auth.css'; interface RegisterForm { firstName: string; lastName: string; + username: string; email: string; password: string; confirm: string; @@ -20,15 +26,19 @@ type OpenDoc = null | 'termos' | 'privacidade'; export function Register() { const navigate = useNavigate(); + const { refreshUser } = useAuth(); const [form, setForm] = useState({ firstName: '', lastName: '', + username: '', email: '', password: '', confirm: '', }); const [agreed, setAgreed] = useState(false); const [openDoc, setOpenDoc] = useState(null); + const [error, setError] = useState(''); + const [submitting, setSubmitting] = useState(false); const set = (field: keyof RegisterForm) => (e: React.ChangeEvent) => @@ -36,13 +46,33 @@ export function Register() { const passwordMismatch = form.confirm !== '' && form.confirm !== form.password; + const passwordWeak = !isPasswordStrong(form.password); - // Gating: signup só ocorre se `agreed` for true E senhas baterem. - // Aceitação dos termos é PRÉ-REQUISITO legal — sem aceite, navegação - // bloqueada pelo botão disabled. - const handleSubmit = (e: React.FormEvent) => { + // Cadastro real: POST /auth/register/ (seta cookie httpOnly + emite tokens), + // depois refreshUser() carrega o usuário no contexto via /auth/me/, então + // navega autenticado pra home. Aceite dos termos + senhas batendo são + // pré-requisito (botão disabled), mas revalidamos aqui por segurança. + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!passwordMismatch && agreed) navigate('/'); + if (passwordMismatch || passwordWeak || !agreed || submitting) return; + setError(''); + setSubmitting(true); + try { + await authService.register({ + email: form.email.trim(), + username: form.username.trim(), + first_name: form.firstName.trim(), + last_name: form.lastName.trim(), + password: form.password, + password2: form.confirm, + }); + await refreshUser(); + navigate('/'); + } catch (err: unknown) { + setError(extractApiError(err, 'Não foi possível criar a conta.')); + } finally { + setSubmitting(false); + } }; return ( @@ -76,6 +106,17 @@ export function Register() { />

    + + + + {error && ( +

    + {error} +

    + )} + diff --git a/src/pages/Auth/ResetPassword.tsx b/src/pages/Auth/ResetPassword.tsx index 21a95b13..85fb6d96 100644 --- a/src/pages/Auth/ResetPassword.tsx +++ b/src/pages/Auth/ResetPassword.tsx @@ -4,6 +4,8 @@ import { AuthLayout } from '@/components/layout/AuthLayout'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { authService } from '@/services/authService'; +import { PasswordChecklist } from '@/components/ui/PasswordChecklist'; +import { isPasswordStrong } from '@/utils/passwordRules'; import './Auth.css'; export function ResetPassword() { @@ -28,6 +30,10 @@ export function ResetPassword() { setError('As senhas não coincidem.'); return; } + if (!isPasswordStrong(form.password)) { + setError('A senha não atende a todos os requisitos de segurança.'); + return; + } if (!token) { setError('Link de recuperação inválido.'); return; @@ -119,6 +125,8 @@ export function ResetPassword() {

+ + {loading ? 'Salvando…' : 'Salvar nova senha'} diff --git a/src/pages/Buscar/Buscar.css b/src/pages/Buscar/Buscar.css new file mode 100644 index 00000000..6282fc83 --- /dev/null +++ b/src/pages/Buscar/Buscar.css @@ -0,0 +1,85 @@ +/* Buscar page — layout container + cabeçalho + lista de resultados. + Sem redefinição de tokens (ADR-029). */ + +.buscar-page { + padding-top: var(--sp-8); + padding-bottom: var(--sp-16); + min-height: 70vh; +} + +.buscar-page__header { + margin-bottom: var(--sp-6); +} + +.buscar-page__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: 700; + color: var(--clr-text); + line-height: 1.2; + margin: 0 0 var(--sp-2); +} + +.buscar-page__subtitle { + font-size: var(--text-base); + color: var(--clr-muted); + margin: 0; +} + +.search-results__header { + font-size: var(--text-sm); + color: var(--clr-subtle); + padding: var(--sp-3) 0; + border-bottom: 1px solid var(--clr-border-soft); + margin-bottom: var(--sp-2); +} + +.search-results__count { + font-weight: 500; +} + +.search-results__list { + list-style: none; + padding: 0; + margin: 0; +} + +.search-results__item { + list-style: none; +} + +.search-results__more { + display: flex; + justify-content: center; + margin-top: var(--sp-6); +} + +.search-results__more-button { + background: var(--clr-surface); + color: var(--clr-primary); + border: 1px solid var(--clr-primary); + padding: var(--sp-3) var(--sp-6); + border-radius: var(--radius-md); + font-weight: 500; + font-size: var(--text-base); + transition: + background var(--t-fast), + color var(--t-fast); +} + +.search-results__more-button:hover:not(:disabled), +.search-results__more-button:focus-visible:not(:disabled) { + background: var(--clr-primary); + color: var(--clr-white); +} + +.search-results__more-button:disabled { + opacity: 0.6; + cursor: progress; +} + +@media (max-width: 640px) { + .buscar-page__title { + font-size: var(--text-3xl); + } +} diff --git a/src/pages/Buscar/Buscar.tsx b/src/pages/Buscar/Buscar.tsx new file mode 100644 index 00000000..36eca112 --- /dev/null +++ b/src/pages/Buscar/Buscar.tsx @@ -0,0 +1,79 @@ +/** + * Página `/buscar` — busca editorial full-text (US30.1). + * + * Estrutura (DESIGN-v3 §2.5): + *
h1 "Buscar" (landmark editorial visível, ADR-029). + * form role="search" + input type="search". + * shell URL-driven; popovers em Sprint 5. + * ADR-030-FE — resilient sub-tree, NÃO envolve input. + * + * orquestra 5 estados. + * + * Pontos finos: + * - `useQueryClient` + `resetKeys={[deferredQ]}` no boundary: cada + * mudança de termo reseta o boundary; bug pontual em um item da SERP + * não trava futuras buscas. + * - SearchInput vive FORA do boundary — usuário sempre pode trocar a + * query mesmo quando os resultados quebraram. + */ +import { Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { useQueryClient } from '@tanstack/react-query'; + +import { SearchInput } from './components/SearchInput'; +import { FilterChips } from './components/FilterChips'; +import { SearchResults } from './components/SearchResults'; +import { SearchErrorFallback } from './components/SearchErrorFallback'; +import { ResultsSkeleton } from './components/Skeletons'; +import { useSearch } from './hooks/useSearch'; + +import './Buscar.css'; +import './components/FilterChips.css'; +import './components/ResultCard.css'; +import './components/SearchStates.css'; + +/** + * Sub-componente para acessar `deferredQ` (sai do hook único) e usar + * como `resetKeys`. Ficar fora do `useSearch` evita duplicar fetch. + */ +function ResultsRegion() { + const { deferredQ } = useSearch(); + const qc = useQueryClient(); + + return ( + { + // Quando o usuário clica "Tentar novamente" OU muda a query, + // invalidamos o cache da chave 'search' para forçar fetch fresh. + qc.resetQueries({ queryKey: ['search'] }); + }} + > + }> + + + + ); +} + +export function Buscar() { + return ( +
+
+

Buscar

+

+ Procure no acervo editorial da Interpop. +

+
+ + + + + + +
+ ); +} + +export default Buscar; diff --git a/src/pages/Buscar/README.md b/src/pages/Buscar/README.md new file mode 100644 index 00000000..dd62125d --- /dev/null +++ b/src/pages/Buscar/README.md @@ -0,0 +1,87 @@ +# `/buscar` — Busca editorial full-text (US30.1) + +Página de busca do Interpop. Spec autoritativa: `docs/specs/busca-editorial/DESIGN.md` v3 (§2.5/§2.6). + +## Estrutura + +``` +src/pages/Buscar/ +├── Buscar.tsx # Página principal (form role=search + ErrorBoundary) +├── Buscar.css +├── index.tsx # Default export para o lazy() do AppRouter +├── types.ts # Tipos do contract OpenAPI do backend +├── README.md # Este arquivo +├── components/ +│ ├── SearchInput.tsx # + URL SSOT (ADR-028) +│ ├── FilterChips.tsx # Shell (Sprint 5 plugará popovers) +│ ├── SearchResults.tsx # Branch dos 5 estados +│ ├── ResultCard.tsx # Card thumb-left 120×80 (ADR-030-UI) +│ ├── HighlightedText.tsx # mark.js + query_terms_expanded (ADR-022) +│ ├── EmptyState.tsx, EmptyResults.tsx, RateLimitedState.tsx +│ ├── SearchErrorFallback.tsx, Skeletons.tsx +├── hooks/ +│ ├── useSearch.ts # TanStack Query + Bug 6 fix (ADR-027) +│ ├── useDebouncedValue.ts # 15 LoC zero-dep +│ ├── useSearchParamsState.ts +├── services/ +│ └── searchService.ts # axios wrapper + SEARCH_STALE_TIME (SSOT) +└── __tests__/ # 78 tests (Buscar, SearchResults, a11y axe-core) +``` + +## Dev local com MSW + +O Vite dev server intercepta `/api/v1/search/articles/` com handlers em `src/mocks/` (BLOQUEIO-1 do REVIEW-PHASE-3 / T30.1.X12). + +**Pré-requisito** (uma vez, já feito): `npx msw init public/ --save` — gera `public/mockServiceWorker.js`. Está commitado no repo. + +**Rodando**: + +```bash +npm run dev # http://localhost:5173/buscar +``` + +O worker registra automaticamente em DEV. No console do browser: `[MSW] Mocking enabled.`. + +### Cenários simulados (`q=...`) + +| Query | Resposta | Uso | +| ---------------- | ------------------------------ | -------------------------------- | +| `kpop` (default) | 10 hits, `total_estimate: 142` | UX feliz | +| `qzxzqzx` | 0 hits, EmptyResults | Validar branch "Nada encontrado" | +| `flood` | 429 + `Retry-After: 23` | Validar RateLimitedState | +| qualquer outra | 10 hits genéricos | Default | + +Latência artificial: 300ms (casa com p50 do DESIGN §0). Sem isso, transição empty→loading→results é invisível. + +### Desligar MSW (apontar para Django local) + +``` +http://localhost:5173/buscar?msw=off&q=kpop +``` + +Reverte ao backend real em `VITE_API_URL` (default `http://localhost:8000`). + +### Produção + +`main.tsx` faz dynamic `import('./mocks/browser')` **apenas** em `import.meta.env.DEV`. Em produção, Vite tree-shakes e `msw` não entra no bundle (verificado via `npm run build` — sem chunk `mocks-*` em `dist/`). + +## ADRs honrados + +- ADR-022 (highlight com `query_terms_expanded`) +- ADR-026 (CSR + lazy route) +- ADR-027 (debounce 250 + URL SSOT + Bug 6 fix) +- ADR-028 (``, rejeita combobox) +- ADR-029 (paleta editorial herdada; sem fork) +- ADR-030-FE (Resilient sub-tree ErrorBoundary) +- ADR-030-UI (chips radius-md + card thumb-left) +- ADR-031-FE (Lighthouse CI gate ≤+20 KB gz — atual: 14.5 KB gz lazy) +- ADR-045 (axe-core nos 5 estados) + +## Tasks futuras (Sprint 5) + +- F-31 filtros funcionais (popover author/category/range datas) +- F-32 deep-linking complexo +- Visual regression Playwright (ADR-042 / T30.1.X20) +- E2E Playwright (T30.1.X21) +- Property-based para `useDebouncedValue` + `canonicalKey` (T30.1.X22) +- i18n extract (T30.1.X24) diff --git a/src/pages/Buscar/__tests__/Buscar.test.tsx b/src/pages/Buscar/__tests__/Buscar.test.tsx new file mode 100644 index 00000000..a0b11b5b --- /dev/null +++ b/src/pages/Buscar/__tests__/Buscar.test.tsx @@ -0,0 +1,69 @@ +/** + * Smoke test integrado da pagina /buscar. + * + * Renderiza Buscar com QueryClient + MemoryRouter reais. Validamos: + * - h1 "Buscar" presente (landmark editorial visivel). + * - SearchInput renderiza no DOM (role=search + role=searchbox). + * - FilterChips shell aparece com "Sem filtros ativos". + * - SearchResults aparece em EmptyState quando ?q nao existe. + * - Sem role="combobox" em nenhum lugar (ADR-028). + */ +import type { ReactNode } from 'react'; +import { describe, it, expect } from 'vitest'; +import { MemoryRouter } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen } from '@testing-library/react'; + +import { Buscar } from '../Buscar'; + +function wrap(node: ReactNode, url = '/buscar') { + const qc = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + return render( + + {node} + , + ); +} + +describe('Buscar — montagem da pagina (DESIGN-v3 §2.5)', () => { + it('renderiza h1 "Buscar" (landmark editorial)', () => { + wrap(); + expect( + screen.getByRole('heading', { level: 1, name: /buscar/i }), + ).toBeDefined(); + }); + + it('renderiza form role="search" com input type="search"', () => { + wrap(); + expect(screen.getByRole('search')).toBeDefined(); + const input = screen.getByRole('searchbox'); + expect(input).toHaveAttribute('type', 'search'); + }); + + it('renderiza FilterChips shell vazia ("Sem filtros ativos")', () => { + wrap(); + expect(screen.getByText(/sem filtros ativos/i)).toBeDefined(); + }); + + it('renderiza EmptyState quando nao ha ?q na URL', () => { + wrap(); + // Texto específico do EmptyState (SearchInput tem prompt similar mas com "Resultados aparecem abaixo") + expect( + screen.getByText(/digite ao menos 2 caracteres para buscar artigos/i), + ).toBeDefined(); + }); + + it('NAO renderiza nenhum role="combobox" (ADR-028)', () => { + const { container } = wrap(); + expect(container.querySelector('[role="combobox"]')).toBeNull(); + expect(container.querySelector('[aria-expanded]')).toBeNull(); + }); + + it('quando ?q presente, ainda renderiza o input pre-populado', () => { + wrap(, '/buscar?q=kpop'); + const input = screen.getByRole('searchbox') as HTMLInputElement; + expect(input.value).toBe('kpop'); + }); +}); diff --git a/src/pages/Buscar/__tests__/SearchResults.test.tsx b/src/pages/Buscar/__tests__/SearchResults.test.tsx new file mode 100644 index 00000000..2406daa9 --- /dev/null +++ b/src/pages/Buscar/__tests__/SearchResults.test.tsx @@ -0,0 +1,169 @@ +/** + * Spec: SearchResults orquestra 5 estados (DESIGN-v3 §2.5). + * + * Estratégia: mockamos `useSearch` para forçar cada branch. Não tocamos + * em rede aqui — o teste do hook real cobre rede + Bug 6 + retry policy + * em `hooks/__tests__/useSearch.test.tsx`. Aqui testamos a renderização. + */ +import type { ReactNode } from 'react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { MemoryRouter } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; + +import { SearchResults } from '../components/SearchResults'; +import * as useSearchModule from '../hooks/useSearch'; +import type { SearchResultPage } from '../types'; + +function wrap(node: ReactNode) { + return {node}; +} + +const page = (overrides: Partial = {}): SearchResultPage => ({ + results: [ + { + id: 'id-1', + title: 'O kpop hoje', + slug: 'kpop-hoje', + excerpt: 'Texto curto.', + published_at: '2026-05-01T00:00:00Z', + author: { id: 'a', name: 'Ana' }, + category: { id: 1, name: 'Música', slug: 'musica' }, + cover_url: null, + score: 0.1, + }, + ], + next_cursor: null, + total_estimate: 1, + query_terms_expanded: ['kpop'], + took_ms: 30, + ...overrides, +}); + +type UseSearchReturn = ReturnType; + +function mockUseSearch(partial: Partial) { + // Cast intencional: o hook real retorna ~25 props do TanStack — + // só nos importam as usadas pelo SearchResults. + vi.spyOn(useSearchModule, 'useSearch').mockReturnValue( + partial as UseSearchReturn, + ); +} + +describe('SearchResults — 5 estados (CA01)', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('Empty: q < 2 chars → EmptyState', () => { + mockUseSearch({ isEnabled: false, deferredQ: 'k' }); + render(wrap()); + expect(screen.getByText(/digite ao menos 2 caracteres/i)).toBeDefined(); + }); + + it('Loading: primeiro fetch → ResultsSkeleton', () => { + mockUseSearch({ + isEnabled: true, + isLoading: true, + isFetching: true, + deferredQ: 'kpop', + }); + render(wrap()); + expect( + screen.getByRole('status', { name: /carregando resultados/i }), + ).toBeDefined(); + }); + + it('NoResults: total_estimate=0 → EmptyResults', () => { + mockUseSearch({ + isEnabled: true, + deferredQ: 'qzxzqzx', + data: { + pages: [page({ results: [], total_estimate: 0 })], + pageParams: [], + }, + }); + render(wrap()); + expect(screen.getByText(/nada encontrado para .qzxzqzx./i)).toBeDefined(); + }); + + it('Results: renderiza cabeçalho + lista de ResultCards', () => { + mockUseSearch({ + isEnabled: true, + deferredQ: 'kpop', + data: { pages: [page()], pageParams: [] }, + }); + render(wrap()); + expect(screen.getByText(/1 resultado/i)).toBeDefined(); + // HighlightedText quebra "O kpop hoje" em ["O ", kpop, " hoje"] + // Buscar via textContent normalizado do link âncora do ResultCard + const link = screen.getByRole('link', { name: /kpop/i }); + expect(link.textContent?.replace(/\s+/g, ' ').trim()).toContain( + 'O kpop hoje', + ); + }); + + it('Plural: total ≠ 1 escreve "resultados"', () => { + mockUseSearch({ + isEnabled: true, + deferredQ: 'kpop', + data: { + pages: [page({ total_estimate: 42 })], + pageParams: [], + }, + }); + render(wrap()); + expect(screen.getByText(/42 resultados/i)).toBeDefined(); + }); + + it('hasNextPage: renderiza botão "Carregar mais"', () => { + mockUseSearch({ + isEnabled: true, + deferredQ: 'kpop', + hasNextPage: true, + data: { pages: [page()], pageParams: [] }, + }); + render(wrap()); + expect( + screen.getByRole('button', { name: /carregar mais/i }), + ).toBeDefined(); + }); + + it('RateLimited (429): renderiza countdown', () => { + mockUseSearch({ + isEnabled: true, + deferredQ: 'kpop', + isError: true, + error: { + isAxiosError: true, + response: { + status: 429, + headers: { 'retry-after': '23' }, + data: { retry_after: 23 }, + }, + } as unknown as Error, + refetch: vi.fn() as unknown as UseSearchReturn['refetch'], + }); + render(wrap()); + expect(screen.getByText(/muitas buscas/i)).toBeDefined(); + expect(screen.getByText(/aguarde 23s/i)).toBeDefined(); + }); + + it('Error genérico: throw para ErrorBoundary', () => { + mockUseSearch({ + isEnabled: true, + deferredQ: 'kpop', + isError: true, + error: new Error('Boom!'), + }); + // React loga o throw em console.error — silenciamos para o test output + // ficar limpo. (O throw é o comportamento esperado.) + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + expect(() => render(wrap())).toThrow('Boom!'); + consoleError.mockRestore(); + }); +}); diff --git a/src/pages/Buscar/__tests__/a11y.test.tsx b/src/pages/Buscar/__tests__/a11y.test.tsx new file mode 100644 index 00000000..c8ee0a6f --- /dev/null +++ b/src/pages/Buscar/__tests__/a11y.test.tsx @@ -0,0 +1,160 @@ +/** + * A11y E2E via `vitest-axe` (fix BLOQUEIO-2 do REVIEW-PHASE-3, ADR-045). + * + * Por que existe: o commit `f0b3f34` declarava `[a11y axe-core]` mas o + * arquivo nunca foi criado — `grep -rn "vitest-axe" src/` retornava + * vazio. O REVIEW-PHASE-3 marcou como BLOQUEIO para o PR US30.1. Esta + * suite fecha o gate. + * + * Estratégia: + * 1. Estados em isolamento (rápido, sem mock TanStack): cobre 5 cenários + * (Empty, Loading via Skeleton, NoResults, RateLimited, Error) + + * componentes-chave (SearchInput, ResultCard, FilterChips). + * 2. Integração leve da página `` sem rede (sem ?q → Empty + * state) — pega regressões de landmark uniqueness (1 h1, 1 search, + * etc.) que axe avalia sobre a árvore inteira. + * + * Visual regression dos 5 estados em viewport real (light + dark + mobile) + * fica para Sprint 5 via Playwright `toHaveScreenshot` (ADR-042). + * NVDA/VoiceOver manual já está no roadmap como TX-20. + */ +import type { ReactNode } from 'react'; +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { axe } from 'vitest-axe'; +// vitest-axe@0.1.0 NÃO auto-extende `expect` ao importar `extend-expect` +// (esse arquivo só declara o tipo no namespace `Vi`, e Vitest 4 usa o +// módulo `vitest` direto). Runtime: chamamos `expect.extend` manualmente. +// Tipo: aumentamos o módulo `vitest` para o TS reconhecer o matcher. +import { toHaveNoViolations } from 'vitest-axe/dist/matchers.js'; + +declare module 'vitest' { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + interface Assertion { + toHaveNoViolations(): T; + } +} + +expect.extend({ toHaveNoViolations }); + +import { EmptyState } from '../components/EmptyState'; +import { EmptyResults } from '../components/EmptyResults'; +import { RateLimitedState } from '../components/RateLimitedState'; +import { SearchErrorFallback } from '../components/SearchErrorFallback'; +import { ResultsSkeleton } from '../components/Skeletons'; +import { SearchInput } from '../components/SearchInput'; +import { ResultCard } from '../components/ResultCard'; +import { FilterChips } from '../components/FilterChips'; +import Buscar from '../Buscar'; +import type { SearchResultItem } from '../types'; + +/** Wrapper mínimo (Router + Query) para componentes que precisam de hooks. */ +function wrap(node: ReactNode, url = '/buscar') { + const qc = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + return ( + + {node} + + ); +} + +const sampleItem: SearchResultItem = { + id: 'id-1', + title: 'O kpop hoje', + slug: 'kpop-hoje', + excerpt: 'Análise editorial sobre o cenário atual.', + published_at: '2026-05-01T00:00:00Z', + author: { id: 'a', name: 'Ana' }, + category: { id: 1, name: 'Música', slug: 'musica' }, + cover_url: null, + score: 0.5, +}; + +describe('A11y axe-core — 5 estados + componentes-chave (ADR-045)', () => { + it('EmptyState: zero violações', async () => { + const { container } = render(); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('Loading (ResultsSkeleton): zero violações', async () => { + const { container } = render(); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('EmptyResults: zero violações', async () => { + const { container } = render(); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('RateLimitedState: zero violações', async () => { + const { container } = render( + {}} />, + ); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('SearchErrorFallback (Error genérico): zero violações', async () => { + const { container } = render( + {}} + />, + ); + expect(await axe(container)).toHaveNoViolations(); + }); +}); + +describe('A11y axe-core — componentes interativos', () => { + it('SearchInput: zero violações (form role="search" + landmark)', async () => { + const { container } = render(wrap()); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('ResultCard com cover ausente (placeholder letra): zero violações', async () => { + const { container } = render( + wrap(), + ); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('ResultCard com cover_url: zero violações (alt="" decorativo)', async () => { + const { container } = render( + wrap( + , + ), + ); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('FilterChips estado vazio: zero violações', async () => { + const { container } = render(wrap()); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('FilterChips com filtros aplicados: zero violações', async () => { + const { container } = render( + wrap(, '/buscar?q=kpop&category=1&author=joao-silva'), + ); + expect(await axe(container)).toHaveNoViolations(); + }); +}); + +describe('A11y axe-core — integração página Buscar', () => { + it('Buscar (Empty inicial): landmark uniqueness, 1 h1, form role=search', async () => { + const { container } = render(wrap()); + expect(await axe(container)).toHaveNoViolations(); + }); + + it('Buscar com ?q válido (Empty enquanto enabled=false ainda): zero violações', async () => { + // q.length=1 < 2 → ainda EmptyState; landmark + input pré-populado. + const { container } = render(wrap(, '/buscar?q=k')); + expect(await axe(container)).toHaveNoViolations(); + }); +}); diff --git a/src/pages/Buscar/components/EmptyResults.tsx b/src/pages/Buscar/components/EmptyResults.tsx new file mode 100644 index 00000000..63efe8f7 --- /dev/null +++ b/src/pages/Buscar/components/EmptyResults.tsx @@ -0,0 +1,27 @@ +/** + * "Nada encontrado para X" — `total_estimate === 0` no payload. + * + * Difere de `EmptyState` (que é o estado inicial). Aqui o backend já + * respondeu — mostramos a query buscada (entre aspas) e uma sugestão + * acionável. Em Sprint 5 podemos adicionar "Você quis dizer Y" baseado + * em `query_terms_expanded` quando o stem diverge da query original. + */ +interface EmptyResultsProps { + query: string; +} + +export function EmptyResults({ query }: EmptyResultsProps) { + return ( +
+

Nada encontrado para “{query}”.

+

+ Tente termos mais gerais (ex.: “kpop” em vez de “kpop 4ª geração”) ou + verifique a grafia. +

+
+ ); +} diff --git a/src/pages/Buscar/components/EmptyState.tsx b/src/pages/Buscar/components/EmptyState.tsx new file mode 100644 index 00000000..8a73c7e9 --- /dev/null +++ b/src/pages/Buscar/components/EmptyState.tsx @@ -0,0 +1,20 @@ +/** + * Estado inicial — antes do usuário digitar (q < 2 chars). + * + * NÃO é "nenhum resultado encontrado" (esse é EmptyResults). Aqui o + * propósito é orientar: "como uso essa página?". Texto curto em pt-BR, + * sem ilustração pesada — busca editorial é ferramenta, não cerimônia. + */ +export function EmptyState() { + return ( +
+

+ Digite ao menos 2 caracteres para buscar artigos. +

+

+ Procure por artistas, álbuns, filmes, livros ou conceitos — encontramos + no acervo Interpop. +

+
+ ); +} diff --git a/src/pages/Buscar/components/FilterChips.css b/src/pages/Buscar/components/FilterChips.css new file mode 100644 index 00000000..e45ce470 --- /dev/null +++ b/src/pages/Buscar/components/FilterChips.css @@ -0,0 +1,48 @@ +/* FilterChips — ADR-030-UI: radius-md (NÃO full-rounded pílula). + Sem redefinição de --clr-primary etc. */ + +.filter-chips { + display: flex; + flex-wrap: wrap; + gap: var(--sp-2); + list-style: none; + padding: 0; + margin: var(--sp-3) 0; +} + +.filter-chips--empty { + font-size: var(--text-sm); + color: var(--clr-subtle); +} + +.filter-chips__empty-label { + font-style: italic; +} + +.filter-chip-item { + list-style: none; +} + +.filter-chip { + display: inline-flex; + align-items: center; + gap: var(--sp-2); + background: var(--clr-chip-bg); + color: var(--clr-chip-on); + border: 1px solid var(--clr-chip-border); + border-radius: var(--radius-md); + padding: var(--sp-1) var(--sp-3); + font-size: var(--text-sm); + font-weight: 500; + transition: background var(--t-fast); +} + +.filter-chip:hover, +.filter-chip:focus-visible { + background: var(--clr-chip-bg-hover); +} + +.filter-chip__x { + font-size: 1.1rem; + line-height: 1; +} diff --git a/src/pages/Buscar/components/FilterChips.tsx b/src/pages/Buscar/components/FilterChips.tsx new file mode 100644 index 00000000..f130eab4 --- /dev/null +++ b/src/pages/Buscar/components/FilterChips.tsx @@ -0,0 +1,117 @@ +/** + * FilterChips — shell do MVP (Sprint 4). + * + * Hoje: lê filtros da URL (`author`, `category`, `de`, `ate`) e renderiza + * 1 chip por filtro presente + botão "Remover" que limpa o param. Estado + * vazio anuncia "Sem filtros ativos" para o usuário ter feedback claro de + * que a busca não está restringida. + * + * Sprint 5 (F-31): plugar popover de seleção (author multiselect, category + * dropdown, date range picker) e overlay mobile. A infra visual fica + * pronta aqui — radius-md (ADR-030-UI: NÃO full-rounded), tokens + * --clr-chip-* dark-mode-ready. + * + * Nota de a11y: cada chip é um + + ))} + + ); +} diff --git a/src/pages/Buscar/components/HighlightedText.tsx b/src/pages/Buscar/components/HighlightedText.tsx new file mode 100644 index 00000000..83684854 --- /dev/null +++ b/src/pages/Buscar/components/HighlightedText.tsx @@ -0,0 +1,89 @@ +/** + * Realça stems de `query_terms_expanded` em um trecho de texto editorial. + * + * Por que existe (ADR-022): o backend NÃO devolve HTML com ; manda + * o texto plano + a lista de stems pt-BR (expandidos via `ts_lexize`). + * O highlight é responsabilidade da UI — assim podemos pintar tanto o + * título quanto o excerpt com as MESMAS regras sem inflar o payload. + * + * Por que mark.js e não regex manual: + * - mark.js percorre apenas nós de TEXTO via `Range`/`Node.splitText`. + * Como nunca toca `innerHTML`, é impossível um termo virar tag + * executável (vide testes `XSS hardening`). Isso fecha o cenário do + * SECURITY-REVIEW §3.4. + * - `accuracy: 'complementary'` permite que o stem 'cantor' case + * 'cantores', 'cantoras' etc. — exatamente o que o backend espera + * porque os termos vêm pós-`ts_lexize` (Invariant #11). + * - `separateWordSearch: false` impede que um termo composto seja + * dividido em pedaços e gere matches falsos. + * + * Implementação intencionalmente mínima: + * - Render um `` (inline, não quebra fluxo do título). + * - Em cada mudança de `text`/`terms`, faz `unmark` e remarca. + * Chave do effect inclui `terms.join('|')` para evitar reaplicar + * quando o array é nova referência mas mesmo conteúdo. + * - StrictMode roda effect 2× em dev — `unmark` na primeira passagem + * garante que não acumulamos aninhados. + */ +import { useEffect, useRef } from 'react'; +import Mark from 'mark.js'; + +interface HighlightedTextProps { + /** Texto plano (título, excerpt). */ + text: string; + /** + * Stems vindos de `query_terms_expanded` do backend. Pode ser undefined + * (carregando) ou vazio (sem busca ativa). + */ + terms: string[] | undefined; + /** Permite que pais marquem o nó para testes. */ + 'data-testid'?: string; +} + +export function HighlightedText({ + text, + terms, + 'data-testid': testId, +}: HighlightedTextProps) { + const ref = useRef(null); + + // Chave estável do array — evita rodar effect quando pai renderiza + // novo array com mesmo conteúdo. + const termsKey = terms?.join('|') ?? ''; + + useEffect(() => { + const node = ref.current; + if (!node) return; + + const instance = new Mark(node); + // Sempre desmarca primeiro: cobre re-render do StrictMode e troca de + // termos em buscas sucessivas (sem isto, marca dentro de marca). + instance.unmark({ + done: () => { + if (!terms || terms.length === 0) return; + instance.mark(terms, { + accuracy: 'complementary', + separateWordSearch: false, + // ADR-022: mark.js renderiza nativo; o estilo vem do CSS + // global (--clr-highlight-bg / --clr-highlight-on) — sem inline. + }); + }, + }); + // Cleanup explícito (fix H-03 do REVIEW-PHASE-3): unmark é async + // (`done` callback). Em re-renders rápidos (paginação infinita), o + // effect anterior pode estar marcando enquanto o novo já começou. + // O cleanup garante DOM limpo antes do próximo run — sem isto, + // observamos kpop aninhado. + return () => { + instance.unmark(); + }; + // text é dependência explícita: se o trecho mudar (paginação, refetch), + // remarca em cima do novo conteúdo. + }, [text, termsKey, terms]); + + return ( + + {text} + + ); +} diff --git a/src/pages/Buscar/components/RateLimitedState.tsx b/src/pages/Buscar/components/RateLimitedState.tsx new file mode 100644 index 00000000..9e41a25b --- /dev/null +++ b/src/pages/Buscar/components/RateLimitedState.tsx @@ -0,0 +1,65 @@ +/** + * 429 com `Retry-After` — backend está esfriando o rate limit. + * + * Mostra um countdown reativo. `retryAfterSeconds` vem do header HTTP ou + * do body `{ retry_after: number }`. Se nada veio, fallback 30s (default + * mais comum do DRF throttle). + * + * UX: NÃO escondemos a query — o usuário ainda vê o que digitou. Botão + * "Tentar agora" só fica enabled quando o contador zera. Antes disso, + * fica desabilitado com o texto do countdown — sem trapacear. + */ +import { useEffect, useState } from 'react'; + +interface RateLimitedStateProps { + /** Segundos para liberar. Pode ser undefined → default 30. */ + retryAfterSeconds?: number; + onRetry: () => void; +} + +const DEFAULT_RETRY_AFTER = 30; + +export function RateLimitedState({ + retryAfterSeconds, + onRetry, +}: RateLimitedStateProps) { + const initial = retryAfterSeconds ?? DEFAULT_RETRY_AFTER; + const [remaining, setRemaining] = useState(initial); + + useEffect(() => { + setRemaining(retryAfterSeconds ?? DEFAULT_RETRY_AFTER); + }, [retryAfterSeconds]); + + useEffect(() => { + if (remaining <= 0) return; + const id = setInterval(() => { + setRemaining((prev) => (prev > 0 ? prev - 1 : 0)); + }, 1000); + return () => clearInterval(id); + }, [remaining]); + + const canRetry = remaining <= 0; + + return ( +
+

Muitas buscas em pouco tempo.

+

+ {canRetry + ? 'Pronto, pode tentar de novo.' + : `Aguarde ${remaining}s para uma nova tentativa.`} +

+ +
+ ); +} diff --git a/src/pages/Buscar/components/ResultCard.css b/src/pages/Buscar/components/ResultCard.css new file mode 100644 index 00000000..b477c4ae --- /dev/null +++ b/src/pages/Buscar/components/ResultCard.css @@ -0,0 +1,176 @@ +/* ResultCard — thumb-LEFT 120×80 (ADR-030-UI). + NÃO redefine --clr-primary / --font-serif / --clr-accent (ADR-029). */ + +.result-card { + display: grid; + grid-template-columns: 120px 1fr; + gap: var(--sp-4); + padding: var(--sp-4) 0; + border-bottom: 1px solid var(--clr-border); +} + +.result-card:last-child { + border-bottom: 0; +} + +.result-card__thumb { + /* Reserva espaço ANTES do download da imagem — anti-CLS. + Sem estes dois, o card "saltaria" 80px quando a imagem chegasse. */ + width: 120px; + height: 80px; + border-radius: var(--radius-md); + overflow: hidden; + background: var(--clr-primary-50); + display: flex; + align-items: center; + justify-content: center; + color: var(--clr-primary); +} + +.result-card__thumb img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.result-card__placeholder { + font-family: var(--font-display); + font-weight: 700; + font-size: var(--text-3xl); + color: var(--clr-primary); + line-height: 1; + user-select: none; +} + +.result-card__body { + display: flex; + flex-direction: column; + gap: var(--sp-2); + min-width: 0; /* permite text-overflow no excerpt */ +} + +.result-card__title { + font-family: var(--font-serif); + font-size: 1.375rem; /* 22px — spec UI/UX */ + font-weight: 600; + line-height: 1.3; + color: var(--clr-text); + margin: 0; +} + +.result-card__title a { + color: inherit; + transition: color var(--t-fast); +} + +.result-card__title a:hover, +.result-card__title a:focus-visible { + color: var(--clr-primary); +} + +/* mark.js insere nativo — estilamos a tag global, não a classe. */ +.result-card__title mark, +.result-card__excerpt mark { + background: var(--clr-highlight-bg); + color: var(--clr-highlight-on); + border-radius: 2px; + padding: 0 2px; +} + +.result-card__excerpt { + font-family: var(--font-serif); + font-size: 0.9375rem; /* 15px */ + color: var(--clr-muted); + line-height: 1.5; + margin: 0; + + /* 2-line clamp: mantém densidade de SERP, evita scroll horizontal. */ + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + line-clamp: 2; + overflow: hidden; +} + +.result-card__meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--sp-2); + font-size: var(--text-sm); + color: var(--clr-subtle); +} + +.result-card__category { + /* Fix H-04: cor herda do token editorial via data-variant no wrapper. + Default usa --clr-primary (navy) quando categoria desconhecida ou ausente. */ + color: var(--cat-color, var(--clr-primary)); + font-weight: 500; +} + +/* Tokens editoriais por categoria (slugs vindos do backend). + Aplicados via custom property `--cat-color` que cascateia para o badge. + Tokens validados WCAG AA em global.css:66-75. */ +.result-card[data-variant='musica'] { + --cat-color: var(--clr-cat-musica); +} +.result-card[data-variant='moda'] { + --cat-color: var(--clr-cat-moda); +} +.result-card[data-variant='cinema'] { + --cat-color: var(--clr-cat-cinema); +} +.result-card[data-variant='literatura'] { + --cat-color: var(--clr-cat-literatura); +} +.result-card[data-variant='cultura-digital'] { + --cat-color: var(--clr-cat-cultura-digital); +} + +/* Placeholder do thumb (sem cover) também pega cor editorial: + identidade visual quando o artigo não tem cover. */ +.result-card[data-variant='musica'] .result-card__placeholder { + color: var(--clr-cat-musica); +} +.result-card[data-variant='moda'] .result-card__placeholder { + color: var(--clr-cat-moda); +} +.result-card[data-variant='cinema'] .result-card__placeholder { + color: var(--clr-cat-cinema); +} +.result-card[data-variant='literatura'] .result-card__placeholder { + color: var(--clr-cat-literatura); +} +.result-card[data-variant='cultura-digital'] .result-card__placeholder { + color: var(--clr-cat-cultura-digital); +} + +/* Mobile: encolhe thumb e título mas mantém legibilidade. */ +@media (max-width: 640px) { + .result-card { + grid-template-columns: 80px 1fr; + gap: var(--sp-3); + } + + .result-card__thumb { + width: 80px; + height: 60px; + } + + .result-card__thumb img { + /* width/height attrs do permanecem 120x80 — o CSS sobrescreve + o tamanho visual mas o browser ainda usou os attrs para reservar + o aspect ratio inicial. */ + width: 100%; + height: 100%; + } + + .result-card__title { + font-size: 1.125rem; /* 18px */ + } + + .result-card__placeholder { + font-size: var(--text-2xl); + } +} diff --git a/src/pages/Buscar/components/ResultCard.tsx b/src/pages/Buscar/components/ResultCard.tsx new file mode 100644 index 00000000..271b434c --- /dev/null +++ b/src/pages/Buscar/components/ResultCard.tsx @@ -0,0 +1,106 @@ +/** + * Card de um resultado de busca — thumb-LEFT 120×80 (ADR-030-UI). + * + * Por que thumb-left e não full-bleed (SECRP-04 §3.1): + * NYT, Folha, Substack reader, ZEIT — SERPs editoriais densas usam + * thumb-left para caber 8-10 cards no viewport. Full-bleed empurra + * tudo abaixo da dobra. Aqui o thumb é informacional (reconhecimento + * editoria) mas o título é o landmark — por isso `alt=""`. + * + * Anti-CLS (ADR-030-UI): + * - declarados como atributos HTML; + * o browser reserva a caixa antes do download e a imagem entra sem + * pushar o título. Sem isso, o card "saltaria" 80px ao carregar. + * - O wrapper `.result-card__thumb` também tem `width: 120px` fixo + * para o caso sem cover (placeholder renderiza um ). + * - Mobile (<=640px): thumb encolhe para 80×60 via media query (CSS). + * + * Hierarquia ARIA: + * - Imagem é decorativa (`alt=""`) — o `
` do título já é o link + * anunciado pelo screen reader. + * - `
+
+
+
+ +
+ ); +} + +interface ResultsSkeletonProps { + count?: number; +} + +export function ResultsSkeleton({ count = 6 }: ResultsSkeletonProps) { + // a11y (axe-core): `role="status"` no
{apiError && (
)} -
+
{/* ── Form ── */} -
@@ -521,7 +551,7 @@ export function CreatePost({ editingSlug }: CreatePostProps = {}) {
)} -
+ ); } diff --git a/src/pages/Home.css b/src/pages/Home.css index 70618d0c..94d5ccce 100644 --- a/src/pages/Home.css +++ b/src/pages/Home.css @@ -115,7 +115,8 @@ text-transform: uppercase; letter-spacing: 0.1em; color: var(--clr-primary); - margin-bottom: var(--sp-5); + /* margin explícito: zera o default do

(mantém só o espaço inferior). */ + margin: 0 0 var(--sp-5); } .home-featured__dot { @@ -247,17 +248,47 @@ transition: all var(--t-fast); } -.home-filters__cat:hover { - border-color: var(--clr-primary); - color: var(--clr-primary); +/* Hover só no chip NÃO-ativo: prévia da cor da editoria (borda + texto), + fundo continua branco. O :not é essencial — sem ele o seletor :hover + (especificidade 0,2,0) vence .home-filters__cat--active (0,1,0) e pinta o + TEXTO com a mesma cor do fundo ativo → palavra sumia ("botão todo escuro"). */ +.home-filters__cat:not(.home-filters__cat--active):hover { + border-color: var(--chip-color, var(--clr-primary)); + color: var(--chip-color, var(--clr-primary)); +} + +/* Cor por categoria — espelha o sistema --clr-cat-* dos badges dos cards. + Chip ativo de "Cinema" fica âmbar (igual ao badge CINEMA), "Moda" terracota, + etc. "Todos" não tem data-category → cai no fallback navy. + Todas as 5 cores passam WCAG AA (>=5:1) com texto branco — verificado. */ +.home-filters__cat[data-category='musica'] { + --chip-color: var(--clr-cat-musica); +} +.home-filters__cat[data-category='moda'] { + --chip-color: var(--clr-cat-moda); +} +.home-filters__cat[data-category='cinema'] { + --chip-color: var(--clr-cat-cinema); +} +.home-filters__cat[data-category='literatura'] { + --chip-color: var(--clr-cat-literatura); +} +.home-filters__cat[data-category='cultura-digital'] { + --chip-color: var(--clr-cat-cultura-digital); } .home-filters__cat--active { - background: var(--clr-primary); - border-color: var(--clr-primary); + background: var(--chip-color, var(--clr-primary)); + border-color: var(--chip-color, var(--clr-primary)); color: var(--clr-white); } +/* Feedback de hover no chip ativo: escurece levemente o fundo sólido sem + tocar na cor do texto — o branco continua legível (contraste só aumenta). */ +.home-filters__cat--active:hover { + filter: brightness(0.92); +} + /* ─── Grid ─── */ .home-grid { display: grid; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4cd37c4b..12f65c10 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -31,7 +31,12 @@ export function Home() { articleService .list({ status: 'published', page: '1' }) .then(({ data }) => { - const feat = data.results.find((a) => a.is_featured) ?? null; + // Híbrido (padrão NYT/Substack): usa o artigo marcado como destaque + // pela curadoria; se NENHUM estiver marcado (esquecimento comum em + // equipe pequena), cai pro mais recente publicado — a home nunca fica + // sem hero. results já vem ordenado por -published_at do backend. + const feat = + data.results.find((a) => a.is_featured) ?? data.results[0] ?? null; setFeatured(feat); // Carousel shows the latest N non-featured articles. The featured // article already has its own dedicated section, so excluding it @@ -80,12 +85,14 @@ export function Home() { {/* Featured */} {featured && ( -
+
-
+ {/* h2 (não div): dá heading real à seção e evita o salto h1→h3 do + título do card destaque (WAVE: "skipped heading level"). */} +
+

diff --git a/src/pages/News.tsx b/src/pages/News.tsx index 8a7d14c7..3ca2f0a9 100644 --- a/src/pages/News.tsx +++ b/src/pages/News.tsx @@ -3,6 +3,7 @@ import { useSearchParams } from 'react-router-dom'; import { PageLayout } from '../components/layout/PageLayout'; import { NewsCard } from '../components/ui/NewsCard'; import { Button } from '../components/ui/Button'; +import { categoryVariant } from '../utils/categoryVariant'; import articleService, { type ApiArticle, type ApiCategory, @@ -195,6 +196,7 @@ export function News() { key={cat.slug} onClick={() => handleCategoryChange(cat.slug)} className={`home-filters__cat ${activeCategory === cat.slug ? 'home-filters__cat--active' : ''}`} + data-category={categoryVariant(cat.slug)} aria-pressed={activeCategory === cat.slug} > {cat.name} @@ -216,7 +218,7 @@ export function News() { ) : articles.length > 0 ? (
{articles.map((article) => ( - + ))}
) : ( diff --git a/src/pages/Perfil.tsx b/src/pages/Perfil.tsx index 51ad7c27..46c86d53 100644 --- a/src/pages/Perfil.tsx +++ b/src/pages/Perfil.tsx @@ -17,9 +17,12 @@ import { Input } from '../components/ui/Input'; import { useAuth } from '../contexts/AuthContext'; import { authService, type ApiUser } from '../services/authService'; import { extractApiError } from '../utils/extractApiError'; +import { PasswordChecklist } from '../components/ui/PasswordChecklist'; +import { isPasswordStrong } from '../utils/passwordRules'; import './Perfil.css'; interface InfoForm { + username: string; first_name: string; last_name: string; bio: string; @@ -37,6 +40,7 @@ export function Perfil() { // Estado local que sincroniza com currentUser. Permite editar sem alterar // o context imediatamente — só atualiza ao salvar com sucesso. const [info, setInfo] = useState({ + username: '', first_name: '', last_name: '', bio: '', @@ -71,6 +75,7 @@ export function Perfil() { useEffect(() => { if (currentUser) { setInfo({ + username: currentUser.username ?? '', first_name: currentUser.first_name, last_name: currentUser.last_name, bio: currentUser.bio ?? '', @@ -113,6 +118,7 @@ export function Perfil() { setInfoFeedback(null); try { const updated = await authService.updateProfile({ + username: info.username.trim(), first_name: info.first_name.trim(), last_name: info.last_name.trim(), bio: info.bio.trim(), @@ -124,6 +130,7 @@ export function Perfil() { // Atualiza valor local com retorno do backend (canônico) const u = updated.data as ApiUser; setInfo({ + username: u.username ?? '', first_name: u.first_name, last_name: u.last_name, bio: u.bio ?? '', @@ -190,10 +197,10 @@ export function Perfil() { setPwFeedback({ type: 'error', msg: 'As senhas novas não coincidem.' }); return; } - if (pwForm.next.length < 8) { + if (!isPasswordStrong(pwForm.next)) { setPwFeedback({ type: 'error', - msg: 'A nova senha precisa ter pelo menos 8 caracteres.', + msg: 'A nova senha não atende a todos os requisitos de segurança.', }); return; } @@ -266,6 +273,22 @@ export function Perfil() { /> + + setInfo((f) => ({ ...f, username: e.target.value })) + } + autoComplete="username" + required + /> +

+ Identificador público único. Aparece como @ + {info.username || 'seu_usuario'} abaixo do seu nome em + comentários e artigos. +

+