From abf8e592c017f5f55d8a780e2dfdca0e433cd00c Mon Sep 17 00:00:00 2001 From: abeer0 <47961062+iiio2@users.noreply.github.com> Date: Wed, 11 Nov 2026 03:13:55 +0000 Subject: [PATCH 01/10] chore: fix typo in code comment (#3417) --- server/api/auth/atproto.get.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/auth/atproto.get.ts b/server/api/auth/atproto.get.ts index 0b58fcc9a1..6888f75b74 100644 --- a/server/api/auth/atproto.get.ts +++ b/server/api/auth/atproto.get.ts @@ -193,7 +193,7 @@ function decodeOAuthState(event: H3Event, state: string | null): OAuthStateData }) } - // The state sting was encoded using encodeOAuthState. No need to protect + // The state string was encoded using encodeOAuthState. No need to protect // against JSON parsing since the StateStore should ensure it's integrity. const decoded = JSON.parse(state) as { data: OAuthStateData; id: string } const requestCookieName = `${OAUTH_REQUEST_COOKIE_PREFIX}_${decoded.id}` From 4b9eb84618a1c74e3c9d0bbbbb96bb9b5fabb56c Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 12 Nov 2026 13:15:39 +0000 Subject: [PATCH 02/10] perf: sample 10% of speed insights events (#3436) --- app/plugins/vercel-speed-insights.client.ts | 8 ++++++++ nuxt.config.ts | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 app/plugins/vercel-speed-insights.client.ts diff --git a/app/plugins/vercel-speed-insights.client.ts b/app/plugins/vercel-speed-insights.client.ts new file mode 100644 index 0000000000..2b29d88f58 --- /dev/null +++ b/app/plugins/vercel-speed-insights.client.ts @@ -0,0 +1,8 @@ +import { injectSpeedInsights } from '@vercel/speed-insights/nuxt/runtime' + +export default defineNuxtPlugin({ + env: { islands: false }, + setup() { + injectSpeedInsights({ sampleRate: 0.1 }) + }, +}) diff --git a/nuxt.config.ts b/nuxt.config.ts index d9aefa1624..ac06bfcb0c 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -6,7 +6,6 @@ const isStorybook = process.env.STORYBOOK === 'true' || process.env.VITEST_STORY export default defineNuxtConfig({ modules: [ - '@vercel/speed-insights', '@unocss/nuxt', 'nuxt-og-image', '@nuxtjs/html-validator', From b077cb7cdbbfb6259b2244d14d02eef50c198b02 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 12 Nov 2026 13:16:14 +0000 Subject: [PATCH 03/10] perf: ignore query in payload isr cache keys (#3437) --- nuxt.config.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index ac06bfcb0c..851e7175d0 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -216,10 +216,17 @@ export default defineNuxtConfig({ passQuery: true, allowQuery: ['activeTab'], }), - '/package/:name/_payload.json': getISRConfig(300, { fallback: 'json' }), - '/package/:name/v/:version/_payload.json': getISRConfig(300, { fallback: 'json' }), - '/package/:org/:name/_payload.json': getISRConfig(300, { fallback: 'json' }), - '/package/:org/:name/v/:version/_payload.json': getISRConfig(300, { fallback: 'json' }), + // payloads do not depend on the query, including the `_b` build id + '/package/:name/_payload.json': getISRConfig(300, { fallback: 'json', allowQuery: [] }), + '/package/:name/v/:version/_payload.json': getISRConfig(300, { + fallback: 'json', + allowQuery: [], + }), + '/package/:org/:name/_payload.json': getISRConfig(300, { fallback: 'json', allowQuery: [] }), + '/package/:org/:name/v/:version/_payload.json': getISRConfig(300, { + fallback: 'json', + allowQuery: [], + }), // infinite cache (versioned - doesn't change) '/package-code/**': { headers: { 'Cache-Control': 'public, s-maxage=31536000, stale-while-revalidate=31536000' }, From 030933df8153631b55db5bb34f8535a0b25224c2 Mon Sep 17 00:00:00 2001 From: Alec Lloyd Probert <55991794+graphieros@users.noreply.github.com> Date: Thu, 12 Nov 2026 18:16:46 +0000 Subject: [PATCH 04/10] feat(a11y): replace hidden data tables with togglable tables for charts (#3430) --- app/components/Compare/FacetBarChart.vue | 1 + app/components/Compare/FacetScatterChart.vue | 3 + .../Package/DailyDownloadsHeatmap.vue | 397 +++++++----- app/components/Package/TimelineChart.vue | 6 + app/components/Package/TrendsChart.vue | 591 ++++++++++-------- app/components/Package/TrendsDataTable.vue | 113 ++++ .../Package/VersionDistribution.vue | 3 + i18n/locales/en.json | 2 + i18n/locales/fr-FR.json | 2 + i18n/schema.json | 6 + package.json | 2 +- pnpm-lock.yaml | 10 +- pnpm-workspace.yaml | 2 +- test/nuxt/a11y.spec.ts | 179 ++++++ 14 files changed, 874 insertions(+), 443 deletions(-) create mode 100644 app/components/Package/TrendsDataTable.vue diff --git a/app/components/Compare/FacetBarChart.vue b/app/components/Compare/FacetBarChart.vue index bc52fda4f2..79e373888d 100644 --- a/app/components/Compare/FacetBarChart.vue +++ b/app/components/Compare/FacetBarChart.vue @@ -142,6 +142,7 @@ const config = computed(() => { return { theme: isDarkMode.value ? 'dark' : '', a11y: { + includeTable: true, translations: { keyboardNavigation: $t('package.trends.chart_assistive_text.keyboard_navigation_vertical'), tableAvailable: $t('package.trends.chart_assistive_text.table_available'), diff --git a/app/components/Compare/FacetScatterChart.vue b/app/components/Compare/FacetScatterChart.vue index 634a2cbaab..dd87cd8a91 100644 --- a/app/components/Compare/FacetScatterChart.vue +++ b/app/components/Compare/FacetScatterChart.vue @@ -136,6 +136,9 @@ function buildExportFilename(extension: string): string { const config = computed(() => { return { + a11y: { + includeTable: true, + }, theme: isDarkMode.value ? 'dark' : '', userOptions: { buttons: { diff --git a/app/components/Package/DailyDownloadsHeatmap.vue b/app/components/Package/DailyDownloadsHeatmap.vue index ab4bdef01b..0991d6fb1d 100644 --- a/app/components/Package/DailyDownloadsHeatmap.vue +++ b/app/components/Package/DailyDownloadsHeatmap.vue @@ -26,6 +26,13 @@ type DailyDataPoint = { const props = defineProps<{ packageName: string }>() const chartRef = useTemplateRef('chartRef') +const showDataTable = shallowRef(false) +const chartPanel = useTemplateRef('chartPanel') +const { height: renderedChartHeight } = useElementSize(chartPanel) +const lastChartHeight = shallowRef(0) +watch(renderedChartHeight, height => { + if (height > 0) lastChartHeight.value = height +}) const { locale, localeProperties } = useI18n() const isRTL = computed(() => localeProperties.value.dir === 'rtl') @@ -219,6 +226,40 @@ const heatmap = computed(() => { const dataset = computed(() => heatmap.value.dataset) +const tableRows = computed(() => { + const yesterday = addDays(toUtcDay(new Date()), -1) + const { weekStarts, dataset: rows, reportedDates, anomalousDates } = heatmap.value + const maximum = Math.max(0, ...rows.flatMap(row => row.values)) + + return weekStarts.flatMap((weekStart, weekIndex) => + WEEK_DAYS.flatMap((_, dayIndex) => { + const date = addDays(parseIsoDate(weekStart), dayIndex) + if (date > yesterday) return [] + const key = toIsoDate(date) + const rawValue = rows[dayIndex]?.values[weekIndex] ?? 0 + const value = + reportedDates.has(key) && !anomalousDates.has(key) && rawValue > 0 ? rawValue : null + return [ + { + date: date.getTime(), + value, + color: + value == null + ? colors.value.bg + : `color-mix(in srgb, ${accent.value} ${maximum > 0 ? (value / maximum) * 100 : 0}%, ${colors.value.bg})`, + }, + ] + }), + ) +}) + +const tableDataset = computed(() => [ + { + name: props.packageName, + series: tableRows.value.map(row => row.value), + }, +]) + function computeMinMax(value: number, maxVal: number, date: string | null) { return { value: compactNumberFormatter.value.format(value), @@ -607,174 +648,208 @@ function getLegendContent(svg: VueUiHeatmapSvgSlotProps['svg']) {
- -
- - - - -