Skip to content

feat: generate alt text in charts#1644

Merged
graphieros merged 18 commits intomainfrom
1264-feat-generate-alt-text-in-charts
Feb 25, 2026
Merged

feat: generate alt text in charts#1644
graphieros merged 18 commits intomainfrom
1264-feat-generate-alt-text-in-charts

Conversation

@graphieros
Copy link
Contributor

@graphieros graphieros commented Feb 25, 2026

Resolves #1264

  • Bump vue-data-ui to 3.15.8 (TS types fine tuning)
  • Add data analysis utilities which correct for outliers before generating analytics that are injected into translation keys
  • Extend the useCharts composable with the alt generation & copy feature

The feature is only applied on the trends charts (modal & compare page) and works for single or multiple series.
We can iterate on other charts (versions).

A new 'Copy alt text' button is added in the chart's contextual menu:

Enregistrement.de.l.ecran.2026-02-25.a.13.11.03.mov
Chart Alt text
image Downloads line chart for the vue package. The Y axis represents the number of downloads. The X axis represents the date range, from 2025-03-04 to 2026-02-24, with a weekly time period. vue starts at 6.5M and ends at 9.1M, showing a mostly flat trend with a slope of 38.5K downloads per time interval, and an overall 32.3% growth. At the bottom, a watermark reads "./npmx a fast, modern browser for the npm registry".
image Packages download comparison line chart between: vue, svelte. The Y axis represents the number of downloads. The X axis represents the date range, from 2025-03-04 to 2026-02-24, with a weekly time period. vue starts at 6.5M and ends at 9.1M, showing a mostly flat trend with a slope of 38.5K downloads per time interval, and an overall 32.3% growth, svelte starts at 2.2M and ends at 3M, showing a mostly flat trend with a slope of 12K downloads per time interval, and an overall 35.2% growth. At the bottom, a watermark reads "./npmx a fast, modern browser for the npm registry".

@graphieros graphieros linked an issue Feb 25, 2026 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 25, 2026 2:39pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 25, 2026 2:39pm
npmx-lunaria Ignored Ignored Feb 25, 2026 2:39pm

Request Review

@github-actions
Copy link

github-actions bot commented Feb 25, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
lunaria/files/en-GB.json Localization changed, will be marked as complete.
lunaria/files/en-US.json Source changed, localizations will be marked as outdated.
lunaria/files/fr-FR.json Localization changed, will be marked as complete.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@graphieros graphieros marked this pull request as draft February 25, 2026 10:58
@graphieros graphieros changed the title 1264 feat generate alt text in charts feat: generate alt text in charts Feb 25, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds alt-text generation and copy-to-clipboard for trend line charts by wiring an altCopy callback and clipboard state into app/components/Package/TrendsChart.vue. Implements statistical utilities (clamp, quantile, winsorize, computeLineChartAnalysis), new dataset/config types, and functions to create/copy alt text in app/utils/charts.ts. Introduces copy_alt translation keys in i18n and lunaria locale files and updates i18n/schema.json. Adds comprehensive unit tests for the chart utilities and bumps vue-data-ui from 3.15.7 to 3.15.8.

Possibly related PRs

Suggested labels

front, a11y

Suggested reviewers

  • danielroe
  • ghostdevv
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly relates to the changeset, detailing implementation of alt-text generation, copy functionality, data analysis utilities, and supporting translations.
Linked Issues check ✅ Passed The PR successfully implements all objectives from issue #1264: generates descriptive alt text from chart data, supports localization via i18n keys, provides copy-to-clipboard functionality, handles single and multi-series charts, and respects character limits.
Out of Scope Changes check ✅ Passed All changes directly support the alt-text generation feature: component integration, utility functions, translations, schema updates, unit tests, and dependency bump for TypeScript improvements are all in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 1264-feat-generate-alt-text-in-charts

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
lunaria/files/fr-FR.json (1)

377-390: Duplicate of the FR wording fix already flagged in the source locale file.

No additional action here beyond keeping this mirror in sync with i18n/locales/fr-FR.json.

lunaria/files/en-GB.json (1)

387-387: Duplicate of the English copy tweak already flagged in i18n/locales/en.json.

Please keep this en-GB mirror aligned when applying that wording update.

🧹 Nitpick comments (2)
app/composables/useCharts.ts (1)

706-707: Consider a defensive cap for copied alt-text length.

The generated text is currently unbounded; adding a hard cap helps meet the shareability target for services with character limits.

♻️ Suggested refinement
-    return (isSinglePackage ? singlePackageText : compareText) + generalAnalysis
+    const text = (isSinglePackage ? singlePackageText : compareText) + generalAnalysis
+    return text.length > 2000 ? `${text.slice(0, 1999)}…` : text
app/utils/charts.ts (1)

471-474: Prefer unknown over any in TrendLineDataset extra fields.

The current index signature removes type checks for all extra properties. unknown keeps flexibility without dropping safety.

♻️ Proposed refinement
 export type TrendLineDataset = {
   lines: VueUiXyDatasetLineItem[]
-  [key: string]: any
+  [key: string]: unknown
 } | null

As per coding guidelines "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index".


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26d967e and 7da1e34.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • app/components/Package/TrendsChart.vue
  • app/composables/useCharts.ts
  • app/utils/charts.ts
  • i18n/locales/en.json
  • i18n/locales/fr-FR.json
  • i18n/schema.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json
  • lunaria/files/fr-FR.json
  • package.json
  • test/unit/app/utils/charts.spec.ts

graphieros and others added 3 commits February 25, 2026 12:55
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
app/utils/charts.ts (3)

247-252: Prefer direct index access over an accumulator loop for the single-element case.

When n === 1, iterating the array with a mutable accumulator is roundabout. As per the coding guidelines, prefer type-safe index access.

♻️ Suggested simplification
-  if (n === 1) {
-    let onlyValue = 0
-    for (const entry of indexedValues) {
-      onlyValue = entry.value
-    }
-
-    return {
-      mean: onlyValue,
+  if (n === 1) {
+    const onlyValue = indexedValues[0]?.value ?? 0
+    return {
+      mean: onlyValue,

471-474: [key: string]: any index signature undermines type safety for TrendLineDataset.

Any property access on a TrendLineDataset will resolve to any, bypassing TypeScript checks entirely. If the intent is to accommodate extra vue-data-ui properties, consider a narrower escape hatch.

♻️ Suggested narrowing
 export type TrendLineDataset = {
   lines: VueUiXyDatasetLineItem[]
-  [key: string]: any
+  // Additional vue-data-ui dataset fields are not consumed here
 } | null

If other properties are genuinely required downstream, document the specific fields instead of using a blanket any index.


514-516: Unnecessary double cast as unknown as string.

ChartTimeGranularity is 'daily' | 'weekly' | 'monthly' | 'yearly', which is directly assignable to string. The intermediate as unknown is redundant.

♻️ Suggested simplification
-    granularityKeyByGranularity[config.granularity as unknown as string] ??
+    granularityKeyByGranularity[config.granularity] ??

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7da1e34 and 036501a.

📒 Files selected for processing (7)
  • app/components/Package/TrendsChart.vue
  • app/utils/charts.ts
  • i18n/locales/en.json
  • i18n/locales/fr-FR.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json
  • lunaria/files/fr-FR.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • lunaria/files/en-GB.json
  • i18n/locales/fr-FR.json
  • i18n/locales/en.json
  • app/components/Package/TrendsChart.vue
  • lunaria/files/fr-FR.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/utils/charts.ts (1)

533-534: ⚠️ Potential issue | 🟡 Minor

?? 0 fallback injects a bare number into a string slot, rendering "0" in alt text.

formattedDatasetValues is Array<string[]>, so when the index is out of range the fallback should be a displayable string, not 0.

🐛 Proposed fix
-        start_value: config.formattedDatasetValues[i]?.[0] ?? 0,
-        end_value: config.formattedDatasetValues[i]?.at(-1) ?? 0,
+        start_value: config.formattedDatasetValues[i]?.[0] ?? '—',
+        end_value: config.formattedDatasetValues[i]?.at(-1) ?? '—',
🧹 Nitpick comments (2)
app/utils/charts.ts (2)

496-501: hasEstimation added to each analysis item is never read.

The per-item hasEstimation: config.hasEstimation spread into every analysis entry is unused inside the .map() callback; the estimation notice is built from config.hasEstimation directly at line 547. This is dead data on every analysis object.

♻️ Suggested removal
  const analysis = dataset.lines.map(({ name, series }) => ({
    name,
    ...computeLineChartAnalysis(series),
    dates: config.formattedDates,
-   hasEstimation: config.hasEstimation,
  }))

503-511: Tighten the granularity map type to Record<ChartTimeGranularity, string>.

config.granularity is already ChartTimeGranularity ('daily' | 'weekly' | 'monthly' | 'yearly'). Using Record<ChartTimeGranularity, string> makes the map exhaustive, lets the compiler catch missing/misspelled keys, and renders the ?? 'package.trends.granularity_weekly' fallback unnecessary.

♻️ Suggested change
-  const granularityKeyByGranularity: Record<string, string> = {
+  const granularityKeyByGranularity: Record<ChartTimeGranularity, string> = {
     daily: 'package.trends.granularity_daily',
     weekly: 'package.trends.granularity_weekly',
     monthly: 'package.trends.granularity_monthly',
     yearly: 'package.trends.granularity_yearly',
   }

-  const granularityKey =
-    granularityKeyByGranularity[config.granularity] ?? 'package.trends.granularity_weekly'
+  const granularityKey = granularityKeyByGranularity[config.granularity]

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 036501a and f4beb99.

📒 Files selected for processing (7)
  • app/utils/charts.ts
  • i18n/locales/en.json
  • i18n/locales/fr-FR.json
  • i18n/schema.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json
  • lunaria/files/fr-FR.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • i18n/schema.json
  • i18n/locales/en.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/utils/charts.ts (1)

172-407: Extract smaller focused helpers from these long functions to reduce responsibilities.

computeLineChartAnalysis (236 lines) and createAltTextForTrendLineChart (80 lines) both exceed the 50-line guideline. Consider splitting them into focused units:

  • computeLineChartAnalysis: Extract statistical calculations (mean, standard deviation), winsorization logic, regression computation, and interpretation rules into separate helpers.
  • createAltTextForTrendLineChart: Extract data transformation, configuration lookup, and text assembly into separate utilities.

This improves maintainability, testability, and readability.


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4beb99 and 942926b.

📒 Files selected for processing (7)
  • app/utils/charts.ts
  • i18n/locales/en.json
  • i18n/locales/fr-FR.json
  • i18n/schema.json
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json
  • lunaria/files/fr-FR.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • lunaria/files/en-GB.json
  • lunaria/files/en-US.json

@codecov
Copy link

codecov bot commented Feb 25, 2026

Codecov Report

❌ Patch coverage is 1.29870% with 152 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/utils/charts.ts 0.00% 111 Missing and 36 partials ⚠️
app/components/Package/TrendsChart.vue 28.57% 4 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 942926b and 6f666a2.

📒 Files selected for processing (2)
  • app/utils/charts.ts
  • test/unit/app/utils/charts.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/utils/charts.ts

Comment on lines +646 to +660
it('upgrades trend from none to weak when relativeSlope is high enough', () => {
// Many points, huge growth + noise to keep linearity low but direction strong.
const values: Array<number | null> = []
for (let i = 0; i < 25; i += 1) {
values.push(i * 1000 + (i % 2 === 0 ? 0 : 8000))
}

const result = computeLineChartAnalysis(values)

expect(result.slope).toBeGreaterThan(0)
expect(result.rSquared).not.toBeNull()
expect(result.interpretation.trend === 'weak' || result.interpretation.trend === 'strong').toBe(
true,
)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The “none → weak” case is under-constrained and can miss regressions.

This test currently allows weak or strong without proving the baseline was none, so it does not fully validate what the title claims.

Suggested tightening
 it('upgrades trend from none to weak when relativeSlope is high enough', () => {
@@
   const result = computeLineChartAnalysis(values)
+  const baseTrend = computeBaseTrend(result.rSquared)
 
+  expect(baseTrend).toBe('none')
   expect(result.slope).toBeGreaterThan(0)
   expect(result.rSquared).not.toBeNull()
-  expect(result.interpretation.trend === 'weak' || result.interpretation.trend === 'strong').toBe(
-    true,
-  )
+  expect(result.interpretation.trend).toBe('weak')
 })

@graphieros graphieros added this pull request to the merge queue Feb 25, 2026
Merged via the queue into main with commit 6d98b52 Feb 25, 2026
21 checks passed
@graphieros graphieros deleted the 1264-feat-generate-alt-text-in-charts branch February 25, 2026 14:47
alex-key pushed a commit to alex-key/npmx.dev that referenced this pull request Feb 25, 2026
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: generate alt text in charts

2 participants