Skip to content

feat(sidebar): Attention widget for major release/breaking change blog posts#6631

Open
siduck wants to merge 2 commits into
developfrom
feat-attention-widget
Open

feat(sidebar): Attention widget for major release/breaking change blog posts#6631
siduck wants to merge 2 commits into
developfrom
feat-attention-widget

Conversation

@siduck

@siduck siduck commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Screenshot

image

Description

  • Blog posts regarding major releases and breaking changes will be written on https://cloud.frappe.io/releases
  • Show lil widget in the sidebar getting the user's attention if the last visited url isnt same as major blog post url
  • Closing the widget or going in its link will save users last visited release page url somewhere

But! Not every blog post on https://cloud.frappe.io/releases will have major changes or is worthy of user's attention. So how do we determine the latest major blog?

  • fetch the first link from the releases page which has attribute of data-major="true"
image




And how will the link get data-major attribute? from the metadata of the page itself !!

image
The static site is written in Astro! https://github.com/frappe/press/tree/master/changelog

Note: right now the user's last visited releasepage* url is stored in localstorage and the widget is hidden on mobiles, I can add a custom field in User doctype but not sure if it'll get messy!

@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a sidebar AttentionWidget that fetches the /releases page, extracts the first data-major="true" anchor via regex, and shows a dismissible banner if the user hasn't seen that post yet (tracked in localStorage).

  • AttentionWidget.vue: new component with fetch-and-parse logic, localStorage tracking, and conditional banner UI.
  • Sidebar.vue: imports and places AttentionWidget inside the nav; removes mt-auto from the bottom bar (now delegated to a spacer div inside the widget).

Confidence Score: 3/5

The widget has a null-href path that causes a broken URL to be opened and permanently re-shows the banner; needs a guard before merging.

When the releases page has no data-major link, href is null but the widget still becomes visible. The first click opens https://cloud.frappe.ionull, and storing null in localStorage means the widget reappears on every load. The fetch also has no error handling, so any network hiccup leaves a silent rejection.

dashboard/src/components/navigation/sidebar/AttentionWidget.vue — null-href guard and fetch error handling.

Important Files Changed

Filename Overview
dashboard/src/components/navigation/sidebar/AttentionWidget.vue New widget that fetches /releases, parses HTML via regex for data-major links, and shows a banner; has a null-href bug that breaks the widget when no major post is found, missing fetch error handling, and a CSS typo.
dashboard/src/components/navigation/sidebar/Sidebar.vue Adds AttentionWidget import/usage and removes mt-auto from the bottom bar (now handled by a spacer div inside AttentionWidget); change is minimal and safe.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant AttentionWidget
    participant ReleasesPage as /releases (HTML)
    participant LocalStorage

    Browser->>AttentionWidget: onMounted
    AttentionWidget->>ReleasesPage: fetch('/releases')
    ReleasesPage-->>AttentionWidget: HTML response
    AttentionWidget->>AttentionWidget: "regex match data-major=true href"
    AttentionWidget->>LocalStorage: read latestReleasePath
    alt "href !== localStorage.latestReleasePath"
        AttentionWidget->>Browser: show banner
        alt user clicks New Releases
            Browser->>AttentionWidget: openUrl()
            AttentionWidget->>Browser: window.open(https://cloud.frappe.io + href)
            AttentionWidget->>LocalStorage: "write latestReleasePath = href"
        else user clicks dismiss
            Browser->>AttentionWidget: setLastBlogPathname()
            AttentionWidget->>LocalStorage: "write latestReleasePath = href"
        end
    end
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
dashboard/src/components/navigation/sidebar/AttentionWidget.vue:16-19
When no `data-major="true"` link exists on the releases page, `href` is `null`. Since `localStorage.latestReleasePath` is `undefined` for first-time visitors, `undefined !== null` is `true`, so `show` becomes `true` and `latestPathName` stays `null`. Clicking "New Releases" then opens `https://cloud.frappe.ionull`. Worse, `setLastBlogPathname` stores the string `"null"` via `localStorage.latestReleasePath = null`, so the widget re-shows on every subsequent visit (since `"null" !== null`).

```suggestion
	if (href && localStorage?.latestReleasePath !== href) {
		latestPathName.value = href
		show.value = true
	}
```

### Issue 2 of 3
dashboard/src/components/navigation/sidebar/AttentionWidget.vue:8-11
The fetch to `/releases` has no error handling — a network failure or non-200 response will produce an unhandled promise rejection that silently breaks the component lifecycle.

```suggestion
const getLatestMajorBlogPath = async () => {
	const url = '/releases'
	try {
		const res = await fetch(url)
		if (!res.ok) return
		const html = await res.text()
```

### Issue 3 of 3
dashboard/src/components/navigation/sidebar/AttentionWidget.vue:40
`hadow` is a typo — Tailwind will not recognize it, so the intended shadow styling is silently dropped.

```suggestion
		class="hidden md:flex flex-col gap-2 p-3 border border-outline-gray-2 dark:border-outline-gray-1 rounded text-sm bg-surface-white dark:bg-surface-cards shadow mt-auto mb-1"
```

Reviews (1): Last reviewed commit: "fix(attention-widget): add .value inside..." | Re-trigger Greptile

Comment on lines +16 to +19
if (localStorage?.latestReleasePath !== href) {
latestPathName.value = href
show.value = true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 When no data-major="true" link exists on the releases page, href is null. Since localStorage.latestReleasePath is undefined for first-time visitors, undefined !== null is true, so show becomes true and latestPathName stays null. Clicking "New Releases" then opens https://cloud.frappe.ionull. Worse, setLastBlogPathname stores the string "null" via localStorage.latestReleasePath = null, so the widget re-shows on every subsequent visit (since "null" !== null).

Suggested change
if (localStorage?.latestReleasePath !== href) {
latestPathName.value = href
show.value = true
}
if (href && localStorage?.latestReleasePath !== href) {
latestPathName.value = href
show.value = true
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: dashboard/src/components/navigation/sidebar/AttentionWidget.vue
Line: 16-19

Comment:
When no `data-major="true"` link exists on the releases page, `href` is `null`. Since `localStorage.latestReleasePath` is `undefined` for first-time visitors, `undefined !== null` is `true`, so `show` becomes `true` and `latestPathName` stays `null`. Clicking "New Releases" then opens `https://cloud.frappe.ionull`. Worse, `setLastBlogPathname` stores the string `"null"` via `localStorage.latestReleasePath = null`, so the widget re-shows on every subsequent visit (since `"null" !== null`).

```suggestion
	if (href && localStorage?.latestReleasePath !== href) {
		latestPathName.value = href
		show.value = true
	}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +8 to +11
const getLatestMajorBlogPath = async () => {
const url = '/releases'
const res = await fetch(url)
const html = await res.text()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The fetch to /releases has no error handling — a network failure or non-200 response will produce an unhandled promise rejection that silently breaks the component lifecycle.

Suggested change
const getLatestMajorBlogPath = async () => {
const url = '/releases'
const res = await fetch(url)
const html = await res.text()
const getLatestMajorBlogPath = async () => {
const url = '/releases'
try {
const res = await fetch(url)
if (!res.ok) return
const html = await res.text()
Prompt To Fix With AI
This is a comment left during a code review.
Path: dashboard/src/components/navigation/sidebar/AttentionWidget.vue
Line: 8-11

Comment:
The fetch to `/releases` has no error handling — a network failure or non-200 response will produce an unhandled promise rejection that silently breaks the component lifecycle.

```suggestion
const getLatestMajorBlogPath = async () => {
	const url = '/releases'
	try {
		const res = await fetch(url)
		if (!res.ok) return
		const html = await res.text()
```

How can I resolve this? If you propose a fix, please make it concise.

<template>
<div class="mt-auto" />
<div
class="hidden md:flex flex-col gap-2 p-3 border border-outline-gray-2 dark:border-outline-gray-1 rounded text-sm bg-surface-white dark:bg-surface-cards hadow mt-auto mb-1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 hadow is a typo — Tailwind will not recognize it, so the intended shadow styling is silently dropped.

Suggested change
class="hidden md:flex flex-col gap-2 p-3 border border-outline-gray-2 dark:border-outline-gray-1 rounded text-sm bg-surface-white dark:bg-surface-cards hadow mt-auto mb-1"
class="hidden md:flex flex-col gap-2 p-3 border border-outline-gray-2 dark:border-outline-gray-1 rounded text-sm bg-surface-white dark:bg-surface-cards shadow mt-auto mb-1"
Prompt To Fix With AI
This is a comment left during a code review.
Path: dashboard/src/components/navigation/sidebar/AttentionWidget.vue
Line: 40

Comment:
`hadow` is a typo — Tailwind will not recognize it, so the intended shadow styling is silently dropped.

```suggestion
		class="hidden md:flex flex-col gap-2 p-3 border border-outline-gray-2 dark:border-outline-gray-1 rounded text-sm bg-surface-white dark:bg-surface-cards shadow mt-auto mb-1"
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.53731% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.88%. Comparing base (666028d) to head (fe3e36c).
⚠️ Report is 9 commits behind head on develop.

Files with missing lines Patch % Lines
.../components/navigation/sidebar/AttentionWidget.vue 93.65% 4 Missing ⚠️
...oard/src/components/navigation/sidebar/Sidebar.vue 75.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           develop    #6631       +/-   ##
============================================
+ Coverage    50.18%   62.88%   +12.70%     
============================================
  Files          990      118      -872     
  Lines        82967    18081    -64886     
  Branches       523      533       +10     
============================================
- Hits         41633    11370    -30263     
+ Misses       41302     6678    -34624     
- Partials        32       33        +1     
Flag Coverage Δ
dashboard 62.88% <92.53%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants