feat: add timeline chart#2663
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new TimelineChart Vue component and chart utilities, integrates it into the package timeline page with hover/focus-driven selectedVersion, introduces a server-side SubEvent type, i18n keys and schema, settings, tests, and a dependency bump. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant TimelineList as Timeline List Item
participant PageState as Page State
participant Chart as TimelineChart Component
participant Export as Export Handler
participant Clipboard as Clipboard/Copy
User->>TimelineList: Hover / Focus version item
TimelineList->>PageState: set selectedVersion(index)
PageState->>Chart: pass selectedVersion prop
Chart->>Chart: build enriched dataset (events, tags, flags)
Chart->>Chart: compute axes, theme, formatters
Chart->>Chart: render series, gradients, SVG markers
Chart->>Chart: highlight datapoint for selectedVersion
Chart->>Chart: show tooltip with version, tags, events
alt User exports chart
User->>Export: click Export (PNG/SVG/CSV)
Export->>Export: generate filename & transform CSV dates
Export->>User: trigger download
end
alt User copies alt-text
User->>Clipboard: click "Copy alt-text"
Clipboard->>Chart: call createAltTextForTimelineChart(dataset, config)
Clipboard->>Clipboard: config.copy(generatedAltText)
Clipboard->>User: alt-text copied
end
User->>TimelineList: Mouse leave / Blur
TimelineList->>PageState: clear selectedVersion
PageState->>Chart: clear highlight
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/pages/package-timeline/[[org]]/[packageName].vue (1)
348-356:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHighlight the whole timeline item, not just the version link.
The chart selection only updates while the pointer or focus is on
LinkBase. Moving across the tags, date, or sub-events clears the highlight, which falls short of the “hovering timeline items” behaviour described in the PR. Attach the handlers to the entry container instead, and usefocusin/focusoutfor keyboard parity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/pages/package-timeline/`[[org]]/[packageName].vue around lines 348 - 356, The hover/focus handlers are currently attached to the LinkBase component so only the version link updates selectedVersion; move the `@mouseenter/`@mouseleave and replace `@focus/`@blur with `@focusin/`@focusout to the timeline entry container element that wraps each entry (the element rendering each entry that contains LinkBase, tags, date and sub-events), and have those handlers set selectedVersion = entry.version on enter/focusin and selectedVersion = null on leave/focusout; also remove the pointer/focus handlers from the LinkBase component so the whole item highlights consistently for mouse and keyboard.
🧹 Nitpick comments (2)
test/nuxt/a11y.spec.ts (1)
1004-1017: ⚡ Quick winAdd one populated-data accessibility case for the new timeline chart.
This test only covers the empty state. A second case with non-empty entries and a selected version would better cover the core chart paths introduced by this feature.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/nuxt/a11y.spec.ts` around lines 1004 - 1017, Add a second test for PackageTimelineChart that checks accessibility when populated: use mountSuspended(PackageTimelineChart, { props: { sizeCache: new Map([[...]]) , versionSubEvents: new Map([[...]]), timelineEntries: [/* at least one realistic entry object matching the component's expected shape */], selectedVersion: /* set to one of the entry versions */ } }) then call runAxe(wrapper) and assert results.violations is empty; reference the existing test case as a template and reuse mountSuspended, PackageTimelineChart, and runAxe to ensure the core chart rendering paths are covered.app/utils/charts.ts (1)
12-12: ⚡ Quick winPrefer a shared type module instead of importing
SubEventfrom an API route file.This creates a brittle app↔server route coupling. Please move
SubEventto a shared types module and import it from there in both layers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/utils/charts.ts` at line 12, The code imports the SubEvent type from a server route which couples app code to API route files; extract SubEvent into a shared types module (e.g., create a new module like types/events or types/shared exporting interface SubEvent) and update all imports to use that module instead of '~~/server/api/registry/timeline/[...pkg].get'; modify the server route that currently declares SubEvent to import it from the new shared module, and change the import in app/utils/charts.ts to import { SubEvent } from the new shared module so both layers reference the same shared type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/Package/TimelineChart.vue`:
- Around line 92-96: The convertedData computed currently iterates
Array.from(props.sizeCache) which preserves Map insertion order and can include
stale points; instead, drive construction from props.timelineEntries: iterate
props.timelineEntries in order, for each entry lookup its size in
props.sizeCache (by version/key) and build the base array passed to
convertMapEntries, then call addTimelineEntries and addEvaluationFlags as before
(preserve the final toReversed() if needed). Update the computed (convertedData)
to use props.timelineEntries as the source of truth and only consult
props.sizeCache for size lookups so X-axis order follows timelineEntries and
stale entries are not introduced.
- Around line 156-165: The dependencyCount series is using smooth: true which
incorrectly interpolates counts; update the series configuration in the
dependencyCount object (where series uses seriesDependencies.value.values and
temperatureColors uses areAllValuesEqual(...)/e18eGradientColors) to remove
smooth: true and instead enable stepped rendering by adding step: true (or the
chart-lib equivalent step option) so the dependency count is drawn as a stepped
series.
- Around line 45-58: The version extraction for entries in the entries.map
callback is incorrect for scoped packages because const version =
entry.name.split('@')[1] only takes the first '@' token; update the logic in
TimelineChart.vue to derive the version by taking the substring after the last
'@' (or better, use an existing separate version property on entry if available)
before calling timelineEntryByVersion.get(version) and populating
time/license/type/hasTypes/hasTrustedPublisher/hasProvenance/tags; adjust the
same parsing at the other occurrence referenced in the comment (line ~98) so
labels, tooltips, active-point lookup and event matching use the correct version
string for scoped packages.
In `@app/utils/charts.ts`:
- Around line 745-756: The code computes overall_progress_percentage without
guarding empty datasets or a zero/undefined baseline; update the block handling
dataset/first/last/firstValue/lastValue so it first returns '' (or early-exits)
if dataset is falsy or dataset.length === 0, and then compute first and last
only after that check; before calculating overall_progress_percentage ensure
firstValue is a finite non-zero number (Number.isFinite(firstValue) &&
firstValue !== 0) and fallback to a safe value or set
overall_progress_percentage to 0 (or null) to avoid Infinity/NaN; update
references to first/last/firstValue/lastValue accordingly so all accesses are
guarded.
In `@i18n/locales/fr-FR.json`:
- Line 584: The French translation in the "general_description" string still
contains the English word "overall"; update that segment so the parentheses read
"(% au total)" (or another appropriate French term like "global") and keep all
placeholders ({metric}, {package}, {first}, {last}, {first_value}, {last_value},
{overall_progress_percentage}, {key_changes}, {watermark}) intact and properly
escaped in the "general_description" value.
---
Outside diff comments:
In `@app/pages/package-timeline/`[[org]]/[packageName].vue:
- Around line 348-356: The hover/focus handlers are currently attached to the
LinkBase component so only the version link updates selectedVersion; move the
`@mouseenter/`@mouseleave and replace `@focus/`@blur with `@focusin/`@focusout to the
timeline entry container element that wraps each entry (the element rendering
each entry that contains LinkBase, tags, date and sub-events), and have those
handlers set selectedVersion = entry.version on enter/focusin and
selectedVersion = null on leave/focusout; also remove the pointer/focus handlers
from the LinkBase component so the whole item highlights consistently for mouse
and keyboard.
---
Nitpick comments:
In `@app/utils/charts.ts`:
- Line 12: The code imports the SubEvent type from a server route which couples
app code to API route files; extract SubEvent into a shared types module (e.g.,
create a new module like types/events or types/shared exporting interface
SubEvent) and update all imports to use that module instead of
'~~/server/api/registry/timeline/[...pkg].get'; modify the server route that
currently declares SubEvent to import it from the new shared module, and change
the import in app/utils/charts.ts to import { SubEvent } from the new shared
module so both layers reference the same shared type.
In `@test/nuxt/a11y.spec.ts`:
- Around line 1004-1017: Add a second test for PackageTimelineChart that checks
accessibility when populated: use mountSuspended(PackageTimelineChart, { props:
{ sizeCache: new Map([[...]]) , versionSubEvents: new Map([[...]]),
timelineEntries: [/* at least one realistic entry object matching the
component's expected shape */], selectedVersion: /* set to one of the entry
versions */ } }) then call runAxe(wrapper) and assert results.violations is
empty; reference the existing test case as a template and reuse mountSuspended,
PackageTimelineChart, and runAxe to ensure the core chart rendering paths are
covered.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2ac11625-634e-418c-936c-349ecbed12de
📒 Files selected for processing (9)
app/components/Package/TimelineChart.vueapp/pages/package-timeline/[[org]]/[packageName].vueapp/utils/charts.tsi18n/locales/en.jsoni18n/locales/fr-FR.jsoni18n/schema.jsonserver/api/registry/timeline/[...pkg].get.tstest/nuxt/a11y.spec.tstest/unit/app/utils/charts.spec.ts
gameroman
left a comment
There was a problem hiding this comment.
The Start y-axis at zero option should probably persisted when reloading the page
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
app/components/Package/TimelineChart.vue (1)
786-786: ⚡ Quick winRely on the global focus-visible rule for this button.
The inline
focus-visible:outline-accent/70utility overrides the project’s shared button/select focus treatment and makes this one control harder to keep visually consistent.♻️ Proposed clean-up
- class="absolute inset-is-1/2 -translate-x-1/2 -bottom-18 sm:inset-is-unset sm:translate-x-0 sm:bottom-auto sm:-inset-ie-20 sm:-top-3 flex items-center justify-center px-2.5 py-1.75 border border-transparent rounded-md text-fg-subtle hover:text-fg transition-colors hover:border-border focus-visible:outline-accent/70 sm:mb-0" + class="absolute inset-is-1/2 -translate-x-1/2 -bottom-18 sm:inset-is-unset sm:translate-x-0 sm:bottom-auto sm:-inset-ie-20 sm:-top-3 flex items-center justify-center px-2.5 py-1.75 border border-transparent rounded-md text-fg-subtle hover:text-fg transition-colors hover:border-border sm:mb-0"Based on learnings: “focus-visible styling for button and select elements is implemented globally in app/assets/main.css … Do not apply per-element inline utility classes like focus-visible:outline-accent/70 on these elements.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/Package/TimelineChart.vue` at line 786, The button in TimelineChart.vue has an inline focus-visible utility ("focus-visible:outline-accent/70") that overrides the global focus-visible rule; remove that utility from the button's class list so the component uses the shared focus styling defined in app/assets/main.css (locate the button element in the TimelineChart.vue template where the long class string contains "focus-visible:outline-accent/70" and delete that token).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/Package/TimelineChart.vue`:
- Line 785: The hard-coded aria-label on the reset button in TimelineChart.vue
(aria-label="reset minimap") must be replaced with a localized string; update
the reset button's aria-label to use the app's i18n lookup (e.g., this.$t or
useI18n's t) with a new translation key like "timeline.resetMinimap" and add
corresponding entries in the locales so screen readers announce the translated
label consistently.
---
Nitpick comments:
In `@app/components/Package/TimelineChart.vue`:
- Line 786: The button in TimelineChart.vue has an inline focus-visible utility
("focus-visible:outline-accent/70") that overrides the global focus-visible
rule; remove that utility from the button's class list so the component uses the
shared focus styling defined in app/assets/main.css (locate the button element
in the TimelineChart.vue template where the long class string contains
"focus-visible:outline-accent/70" and delete that token).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b9b20372-cec5-4ab3-98fe-03143e0c0815
📒 Files selected for processing (1)
app/components/Package/TimelineChart.vue
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
app/components/Package/TimelineChart.vue (1)
811-815: ⚡ Quick winDrop the per-button focus ring override.
This native button already gets the project-wide focus-visible treatment. Keeping
focus-visible:outline-accent/70here makes this control drift from the rest of the button styling.Proposed fix
- class="absolute inset-is-1/2 -translate-x-1/2 -bottom-18 sm:inset-is-unset sm:translate-x-0 sm:bottom-auto sm:-inset-ie-20 sm:-top-3 flex items-center justify-center px-2.5 py-1.75 border border-transparent rounded-md text-fg-subtle hover:text-fg transition-colors hover:border-border focus-visible:outline-accent/70 sm:mb-0" + class="absolute inset-is-1/2 -translate-x-1/2 -bottom-18 sm:inset-is-unset sm:translate-x-0 sm:bottom-auto sm:-inset-ie-20 sm:-top-3 flex items-center justify-center px-2.5 py-1.75 border border-transparent rounded-md text-fg-subtle hover:text-fg transition-colors hover:border-border sm:mb-0"Based on learnings: focus-visible styling for button and select elements is applied globally via
app/assets/main.css, and per-element inline focus-visible utilities should not be added.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/components/Package/TimelineChart.vue` around lines 811 - 815, The button element in TimelineChart.vue currently includes a per-button focus ring utility ("focus-visible:outline-accent/70") which conflicts with the global focus-visible styling; remove the "focus-visible:outline-accent/70" class from that button (the button with aria-label $t('package.timeline.chart.reset_minimap') and class list shown) so it relies on the project-wide focus-visible rules in app/assets/main.css, keeping the rest of the classes and the style="pointer-events: all !important" unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/components/Package/TimelineChart.vue`:
- Around line 597-605: The tooltip currently renders event.text verbatim, but
SubEvent.text is an i18n key; update the rendering in TimelineChart.vue to call
the translation function for the tooltip (use $t(event.text) or the component's
i18n helper) so the displayed string matches the translated copy (consistent
with app/utils/charts.ts which does $t(e.text)); locate the template that
outputs {{ event.text }} and replace it with a translated lookup using the
component's $t or i18n method.
---
Nitpick comments:
In `@app/components/Package/TimelineChart.vue`:
- Around line 811-815: The button element in TimelineChart.vue currently
includes a per-button focus ring utility ("focus-visible:outline-accent/70")
which conflicts with the global focus-visible styling; remove the
"focus-visible:outline-accent/70" class from that button (the button with
aria-label $t('package.timeline.chart.reset_minimap') and class list shown) so
it relies on the project-wide focus-visible rules in app/assets/main.css,
keeping the rest of the classes and the style="pointer-events: all !important"
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4e3c25c1-f2b5-42d2-b013-e94842b0bb03
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
app/components/Package/TimelineChart.vuei18n/locales/en.jsoni18n/locales/fr-FR.jsoni18n/schema.jsonpackage.json
✅ Files skipped from review due to trivial changes (2)
- package.json
- i18n/schema.json
🚧 Files skipped from review as they are similar to previous changes (1)
- i18n/locales/en.json
…into timeline-chart
…into timeline-chart
@gameroman this is now fixed |



Resolves #2658
This adds a line chart to the timeline page.
Features:
Other: