Skip to content
Draft
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: 9 additions & 0 deletions packages/docs/content/docs/internal/design-system.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ exposeTo: [user]
---

import { H1, H2, Description } from '@/src/components/typography'
import { ReleaseImpacts } from '@/src/components/changelog/release-impacts'
import { KNOWN_IMPACT_LABELS } from 'scripts/lib/changelog-dto'

<Description>Who needs Storybook anyway? (using the `<Description>` component)</Description>

Expand Down Expand Up @@ -74,3 +76,10 @@ export const yep = "we got'em too."
<FeatureSupportMatrix introducedInVersion='2.5.0' deprecatedInVersion='2.6.0' support={{ supported: true, frameworks: ['Next.js (app router)']}}/>
<FeatureSupportMatrix introducedInVersion='2.5.0' deprecatedInVersion='2.6.0' support={{ supported: false, frameworks: ['Next.js (app router)']}}/>

## Changelog impact labels

All known impact labels, in changelog display order (features, then parsers,
then adapters), hue-matched to their GitHub labels:

<ReleaseImpacts labels={KNOWN_IMPACT_LABELS} />

1 change: 0 additions & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"build": "pnpm run build:registry && pnpm run build:next",
"build:next": "next build",
"build:registry": "./src/registry/assemble.ts && shadcn build",
"gen:schema": "./src/schemas/changelog-dto.gen.ts",
"start": "next start",
"postinstall": "fumadocs-mdx",
"test": "fumadocs-mdx && next typegen && fumadocs-mdx && tsc && vitest run"
Expand Down
6 changes: 3 additions & 3 deletions packages/docs/public/schemas/changelog-dto.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"author",
"closingIssues"
],
"additionalProperties": false
"additionalProperties": {}
},
{
"type": "object",
Expand Down Expand Up @@ -109,7 +109,7 @@
"description",
"author"
],
"additionalProperties": false
"additionalProperties": {}
}
]
}
Expand All @@ -131,5 +131,5 @@
"contributors",
"$schema"
],
"additionalProperties": false
"additionalProperties": {}
}
16 changes: 13 additions & 3 deletions packages/docs/src/app/docs/changelog/_lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
type Category,
type Change,
groupChangesByCategory,
type KnownImpactLabel,
parseChangelogComment,
releaseImpacts,
stripChangelogComment
} from 'scripts/lib/changelog-dto'
import { z } from 'zod'
Expand Down Expand Up @@ -60,12 +62,13 @@ export async function fetchReleases(): Promise<GithubRelease[]> {
// The render model for one release. `grouped` is null when the release has no
// valid DTO (missing/malformed/unrecognized) — the page renders a degraded entry
// (title + date + link) for it rather than dropping it or failing the build.
// `contributors` is empty in that degraded case (no footer).
// `contributors` and `impacts` are empty in that degraded case.
export type ReleaseModel = {
release: GithubRelease
grouped: Record<Category, Change[]> | null
preamble: string | null
contributors: string[]
impacts: KnownImpactLabel[]
}

export function buildReleaseModel(release: GithubRelease): ReleaseModel {
Expand All @@ -83,13 +86,20 @@ export function buildReleaseModel(release: GithubRelease): ReleaseModel {
release.tag_name
)
}
return { release, grouped: null, preamble: null, contributors: [] }
return {
release,
grouped: null,
preamble: null,
contributors: [],
impacts: []
}
}
return {
release,
grouped: groupChangesByCategory(parsed.dto.changes),
preamble: parsed.preamble,
contributors: parsed.dto.contributors
contributors: parsed.dto.contributors,
impacts: releaseImpacts(parsed.dto.changes)
}
}

Expand Down
8 changes: 7 additions & 1 deletion packages/docs/src/app/docs/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CommitLine } from '@/src/components/changelog/commit-line'
import { ContributorsFooter } from '@/src/components/changelog/contributors-footer'
import { PullRequestLine } from '@/src/components/changelog/pr-line'
import { Preamble } from '@/src/components/changelog/preamble'
import { ReleaseImpacts } from '@/src/components/changelog/release-impacts'
import { github } from '@/src/lib/utils'
import { Heading } from 'fumadocs-ui/components/heading'
import {
Expand Down Expand Up @@ -99,7 +100,10 @@ export default async function ChangelogPage() {
) : (
<div className="space-y-10 pb-12 sm:space-y-16">
{models.map(
({ release, grouped, contributors, preamble }, index) => {
(
{ release, grouped, contributors, preamble, impacts },
index
) => {
const date = formatDate(release.published_at)
const tag = release.tag_name
const title = release.name || tag
Expand Down Expand Up @@ -133,6 +137,8 @@ export default async function ChangelogPage() {
</a>
</div>

<ReleaseImpacts labels={impacts} />

{preamble && (
<div className="mt-6">
<Preamble markdown={preamble} />
Expand Down
43 changes: 43 additions & 0 deletions packages/docs/src/components/changelog/label-classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { KnownImpactLabel } from 'scripts/lib/changelog-dto'

// Tailwind classes for each impact label badge, hue-matched to the GitHub
// label colors of the 47ng/nuqs repo (refresh against `gh label list` if
// those change). Contrast is a review/test concern, not a runtime one: stick
// to the *-50/*-900 band in light and *-950/*-200 in dark. Monochrome brands
// (Next.js/Vercel are strictly black & white) get no fill at all.

const green =
'border-green-600/50 bg-green-50 text-green-900 dark:border-green-700 dark:bg-green-950 dark:text-green-200'
const monochrome = 'border-foreground/40 bg-transparent text-foreground'
const indigo =
'border-indigo-600/50 bg-indigo-50 text-indigo-900 dark:border-indigo-700 dark:bg-indigo-950 dark:text-indigo-200'
const cyan =
'border-cyan-600/50 bg-cyan-50 text-cyan-900 dark:border-cyan-700 dark:bg-cyan-950 dark:text-cyan-200'
const red =
'border-red-600/50 bg-red-50 text-red-900 dark:border-red-700 dark:bg-red-950 dark:text-red-200'
const fuchsia =
'border-fuchsia-600/50 bg-fuchsia-50 text-fuchsia-900 dark:border-fuchsia-700 dark:bg-fuchsia-950 dark:text-fuchsia-200'
const emerald =
'border-emerald-600/50 bg-emerald-50 text-emerald-900 dark:border-emerald-700 dark:bg-emerald-950 dark:text-emerald-200'
const amber =
'border-amber-600/50 bg-amber-50 text-amber-900 dark:border-amber-700 dark:bg-amber-950 dark:text-amber-200'
const gray =
'border-zinc-600/50 bg-zinc-50 text-zinc-900 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200'

export const LABEL_CLASSES: Record<KnownImpactLabel, string> = {
'feature/useQueryState': green,
'feature/useQueryStates': green,
'feature/serializer': green,
'feature/cache': green,
'feature/time-safety': green,
'parsers/built-in': monochrome,
'parsers/community': indigo,
'adapters/next/app': monochrome,
'adapters/next/pages': monochrome,
'adapters/react': cyan,
'adapters/react-router': red,
'adapters/remix': fuchsia,
'adapters/tanstack-router': emerald,
'adapters/testing': amber,
'adapters/community': gray
}
32 changes: 32 additions & 0 deletions packages/docs/src/components/changelog/release-impacts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Badge } from '@/src/components/ui/badge'
import { cn } from '@/src/lib/utils'
import {
formatImpactLabel,
type KnownImpactLabel
} from 'scripts/lib/changelog-dto'
import { LABEL_CLASSES } from './label-classes'

export type ReleaseImpactsProps = {
labels: readonly KnownImpactLabel[]
}

// Per-release impacts row, built entirely from the DTO's per-change labels —
// zero GitHub API calls. Releases published before labels were captured carry
// none and render nothing.
export function ReleaseImpacts({ labels }: ReleaseImpactsProps) {
if (labels.length === 0) return null
return (
<div className="not-prose mt-3 flex flex-wrap items-center gap-x-2 gap-y-1.5">
<span className="text-fd-muted-foreground text-sm">Impacts</span>
{labels.map(label => (
<Badge
key={label}
variant="outline"
className={cn('whitespace-nowrap', LABEL_CLASSES[label])}
>
{formatImpactLabel(label)}
</Badge>
))}
</div>
)
}
15 changes: 0 additions & 15 deletions packages/docs/src/schemas/changelog-dto.artifact.ts

This file was deleted.

20 changes: 0 additions & 20 deletions packages/docs/src/schemas/changelog-dto.gen.ts

This file was deleted.

17 changes: 0 additions & 17 deletions packages/docs/src/schemas/changelog-dto.test.ts

This file was deleted.

14 changes: 13 additions & 1 deletion packages/scripts/UBIQUITOUS_LANGUAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ reworded to reshape the changelog wording **without** amending the commit —
intended and supported. Nothing — not type, not category, not bump — is ever
interpreted from it.

### impact label

A GitHub label on a squashed PR naming a functional area the change touches —
one of the closed vocabulary in `KNOWN_IMPACT_LABELS`, whose array order is
the display order. `discoverChanges` stores the PR's raw GitHub labels per
**change** in the DTO; the release-level **impacts** list is derived at render
by filtering and aggregating those labels, like **category**. Triage labels
(`bug`, `deploy:preview`, …) are workflow noise, never impact. Rendered as
display names ("React Router", "useQueryStates"); a new label on GitHub renders
nowhere until added to the vocabulary, which forces a display name and badge
class at compile time.

### channel

The release channel — `stable` or `beta`. Selects the asymmetric commit range
Expand All @@ -145,7 +157,7 @@ A keyword and a same-repo number in a pull request body that names a **target**
`Closes #N`, `Fixes #N`, `Resolves #N`, `Addresses #N`, and their tenses. The
close/fix/resolve keywords are GitHub's own closing keywords (GitHub links the
issue itself as one of the PR's **closing issues**); `Addresses` is not — GitHub
never links it — but a release *announces* its targets rather than closing them,
never links it — but a release _announces_ its targets rather than closing them,
so we treat it the same. A target reference to a **discussion** is the sole record
that the discussion belongs to the release.

Expand Down
9 changes: 8 additions & 1 deletion packages/scripts/lib/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,25 @@ const descriptionSchema = z.string().min(1).max(500)

const shaSchema = z.string().regex(/^[0-9a-f]{7,40}$/)

// A GitHub label name: non-empty, bounded (GitHub caps labels at 50 chars).
const labelSchema = z.string().min(1).max(50)

// A change sourced from a squashed pull request (the common case):
// identity is the PR number, `description` is the PR title as prose,
// `author` is the GitHub login (or null for a deleted account), and it
// carries the issue numbers it closes. Rendered as `#123 - …, by @login`.
// `labels` are the PR's raw GitHub labels. They are filtered into the impact
// taxonomy only when a release-level impact list is rendered. The default keeps
// release bodies published before this field was added readable.
export const squashedPRChangeSchema = z.strictObject({
source: z.literal('squashedPR'),
prNumber: z.number().int().positive(),
type: z.string().nullable(), // conventional-commit type, or null when none
breaking: z.boolean(),
description: descriptionSchema, // the PR title
author: githubLoginSchema.nullable(), // null for a deleted account
closingIssues: z.array(z.number().int().positive())
closingIssues: z.array(z.number().int().positive()),
labels: z.array(labelSchema).default([])
})

// A change sourced from a direct commit with no PR (rare: a hotfix, a revert)
Expand Down
Loading