Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion web/src/components/whats-new/WhatsNew.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { Megaphone } from 'lucide-react'
import { nextSeenVersion, whatsNewToShow } from './WhatsNew'
import { latestReleaseNotesFor, RELEASE_NOTES, releaseNotesFor, type ReleaseNotes } from './releaseNotes'
import { latestReleaseNotesFor, releaseLine, RELEASE_NOTES, releaseNotesFor, type ReleaseNotes } from './releaseNotes'
import { compareVersions } from '../../utils/version'

const entry = (version: string): ReleaseNotes => ({
Expand Down Expand Up @@ -101,3 +101,10 @@ describe('the shipped catalog', () => {
}
})
})

describe('releaseLine', () => {
it('names the minor line, since its patches show the same notes', () => {
expect(releaseLine('v1.15.0')).toBe('v1.15')
expect(releaseLine('v2.0.0')).toBe('v2.0')
})
})
14 changes: 11 additions & 3 deletions web/src/components/whats-new/WhatsNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DialogPortal } from '@skyhook-io/k8s-ui'
import { getApiBase } from '../../api/config'
import { markWhatsNewSeen, useCapabilities, useWhatsNewState, type WhatsNewState } from '../../api/client'
import { compareVersions } from '../../utils/version'
import { latestReleaseNotesFor, releaseNotesFor, RELEASE_NOTES, type ReleaseHighlight, type ReleaseNotes } from './releaseNotes'
import { latestReleaseNotesFor, releaseLine, releaseNotesFor, RELEASE_NOTES, type HighlightTone, type ReleaseHighlight, type ReleaseNotes } from './releaseNotes'
import type { UsageDataStatus } from '../../api/usage-data'
import { UsageDataAsk } from '../usage-data/UsageDataAsk'

Expand Down Expand Up @@ -284,7 +284,7 @@ export function WhatsNewContent({ titleId, notes, previousVersion, currentVersio
</div>
<div className="min-w-0">
<h2 id={titleId} className="text-lg font-semibold text-theme-text-primary leading-tight">
What's new in Radar <span className="font-mono">{notes.version}</span>
What's new in Radar <span className="font-mono">{releaseLine(notes.version)}</span>
</h2>
{from ? (
<p className="flex flex-wrap items-center gap-1.5 mt-1.5 text-xs text-theme-text-tertiary">
Expand Down Expand Up @@ -361,6 +361,14 @@ export function WhatsNewContent({ titleId, notes, previousVersion, currentVersio
)
}

// Literal class strings so Tailwind keeps them.
const TONE_TILE: Record<HighlightTone, string> = {
violet: 'bg-violet-500/10 text-violet-600 dark:text-violet-400',
teal: 'bg-teal-500/10 text-teal-600 dark:text-teal-400',
emerald: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400',
indigo: 'bg-indigo-500/10 text-indigo-600 dark:text-indigo-400',
}

function HighlightCard({ item, lead = false, onNavigate }: {
item: ReleaseHighlight
lead?: boolean
Expand All @@ -378,7 +386,7 @@ function HighlightCard({ item, lead = false, onNavigate }: {
<>
<span className={clsx(
'flex items-center justify-center shrink-0 rounded-lg',
lead ? 'w-10 h-10 bg-accent text-white' : 'w-8 h-8 bg-accent-muted text-accent',
lead ? 'w-10 h-10 bg-accent text-white' : clsx('w-8 h-8', item.tone ? TONE_TILE[item.tone] : 'bg-accent-muted text-accent'),
)}>
<Icon className={lead ? 'w-5 h-5' : 'w-4 h-4'} aria-hidden />
</span>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/whats-new/WhatsNewDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('WhatsNew', () => {
RELEASE_NOTES.push({ ...RELEASE_NOTES[0], version: 'v1.14.0' })
serverState.seenVersion = 'v1.14.1'
await render('/resources/pods?whats-new=v1.14.0')
expect(dialog()?.textContent).toContain('v1.14.0')
expect(dialog()?.textContent).toContain("What's new in Radar v1.14")
await clickGotIt()
expect(seenPosts).toEqual([])
expect(status.current.unread).toBe(true)
Expand Down
72 changes: 71 additions & 1 deletion web/src/components/whats-new/releaseNotes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { LucideIcon } from 'lucide-react'
import { Bot, GitCompareArrows, Layers, Network, Scale } from 'lucide-react'
import { compareVersions } from '../../utils/version'

export interface ReleaseHighlight {
Expand All @@ -9,8 +10,12 @@ export interface ReleaseHighlight {
/** In-app route the highlight's call to action opens. */
path?: string
cta?: string
/** Icon tile color, so the cards read apart at a glance. The lead card always uses the accent. */
tone?: HighlightTone
}

export type HighlightTone = 'violet' | 'teal' | 'emerald' | 'indigo'

export interface ReleaseNotes {
version: string
/** The first highlight leads the dialog at full width. */
Expand All @@ -24,7 +29,67 @@ export interface ReleaseNotes {
// that skips the release with notes) still gets them. Every minor and major
// release must have its own entry: scripts/check-whats-new.sh refuses to
// release one without it.
export const RELEASE_NOTES: ReleaseNotes[] = []
export const RELEASE_NOTES: ReleaseNotes[] = [
{
version: 'v1.15.0',
releaseUrl: 'https://github.com/skyhook-io/radar/releases/tag/v1.15.0',
highlights: [
{
id: 'ai-investigations',
icon: Bot,
title: 'AI investigations that show their work',
// No link: the investigations workspace redirects home when this run
// mode can't host local agents.
description: 'A clear verdict and the story of what broke, with the charts, logs and config behind it placed where the agent cites them. Now also runs on OpenCode, including AWS Bedrock.',
},
{
id: 'gitops-manifest-diff',
icon: GitCompareArrows,
title: 'Full manifest diffs for Argo CD resources',
description: 'Compare the Git-rendered and live manifest of a drifted Argo CD resource, side by side or unified.',
tone: 'violet',
path: '/gitops',
cta: 'Open GitOps',
},
{
id: 'rightsizing-risks-first',
icon: Scale,
title: 'Rightsizing and cost data in MCP',
description: 'Agents get new optimized tools. Risks (OOM, limit conflicts and throttling) lead before cost-saving, in the UI as well.',
tone: 'emerald',
path: '/cost/rightsizing',
cta: 'Open Rightsizing',
},
{
id: 'batch-ai-workloads',
icon: Layers,
title: 'Deeper batch + AI/ML workloads support',
description: 'Dedicated pages for Kueue, JobSet and Ray resources, and Job and JobSet details now show why a queued job has no Pods.',
tone: 'teal',
},
{
id: 'rollout-traffic-topology',
icon: Network,
title: 'Canary and blue-green in Topology',
description: 'See canary and stable Pods, which Services route to each side, and the canary traffic weight.',
tone: 'indigo',
path: '/topology',
cta: 'Open Topology',
},
],
improvements: [
// The grid fills row by row: pair lines of similar length.
'Resource tables remember your sort for each kind',
'Install Helm charts from OCI registries',
'Checks flag expired and expiring TLS certificates',
'Radar Desktop keeps your preferences across restarts',
'Issues show where a failing Pod differs from its template',
'PVC details say why usage data is unavailable',
'Issues catch Strimzi connector and admission webhook failures',
'Bind the web UI to a specific IP with --listen-address',
],
},
]

export function releaseNotesFor(
version: string | undefined,
Expand All @@ -35,6 +100,11 @@ export function releaseNotesFor(
return catalog.find(notes => notes.version === normalized)
}

/** The release line a catalog entry covers, e.g. v1.15 for v1.15.0: its patches show the same notes. */
export function releaseLine(version: string): string {
return version.replace(/^(v\d+\.\d+)\.\d+$/, '$1')
}

/** The newest notes for a release at or below `version` — what that version's user has access to. */
export function latestReleaseNotesFor(
version: string | undefined,
Expand Down
Loading