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
7 changes: 5 additions & 2 deletions docs/skills/variant-docs-pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ a unified 5-part composition:
identity headers and leading `# Title` by `scripts/fetch-readmes.mjs`.
3. `## Downloads & Image Versions`: `<ProductDownloadsAndVersions />` displays
major component cards with bump badges, plus available ISO cards (Stable
and/or Testing depending on image type, or a DDI notice for server).
and/or Testing depending on image type) or versioned installer/DDI/netboot cards
for Bluefin Server.
Installer publication channel differs from embedded payload channel (e.g.
Dakota stable payload served from the testing CDN).
4. `## Usage`: `<PerImageUsage />` embeds first-party daily active systems with
per-family filtering and explicit unavailable fallbacks.
5. `## Release Assets & Attestations`: `<ProductReleaseAssets />` renders
downloadable release artifacts, bootc/DDI switches, and provenance verification.
downloadable release artifacts, bootc/DDI switches, and provenance verification,
with a left-rail timeline of the latest releases for Server.

Keep headings as Markdown `##` in the `.mdx` file so Docusaurus's build-time AST
populates the right-hand Table of Contents.
Expand Down Expand Up @@ -166,6 +168,7 @@ card entry from `IMAGES` in `ImageCards.tsx`.
explicit, and link build-time recipes to upstream repositories. When headings
are simplified, place explicit HTML anchors (`<a id="..."></a>`) so legacy links
do not 404.

## Common Rationalizations

| Rationalization | Reality |
Expand Down
62 changes: 62 additions & 0 deletions scripts/catalog-components.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ function loadComponent(tsxPath, overrides = {}) {
if (id.startsWith("@site/")) {
return require(path.join(__dirname, "..", id.replace(/^@site\//, "")));
}
if (id.startsWith("./") || id.startsWith("../")) {
const resolvedPath = path.resolve(path.dirname(tsxPath), id);
if (fs.existsSync(resolvedPath + ".tsx")) {
return loadComponent(resolvedPath + ".tsx", overrides);
}
if (fs.existsSync(resolvedPath + ".ts")) {
return loadComponent(resolvedPath + ".ts", overrides);
}
return require(resolvedPath);
}
return require(id);
},
mod,
Expand Down Expand Up @@ -543,3 +553,55 @@ test("DriverVersionsCatalog renders bluefin-server with systemd, sysexts, and sy
"sysupdate deployment note rendered instead of bootc switch",
);
});
const ImageProductSection = loadComponent(
path.join(COMPONENTS_DIR, "ImageProductSection.tsx"),
);

test("ProductDownloadsAndVersions and ProductReleaseAssets render server download card and 7-release timeline", () => {
const { ProductDownloadsAndVersions, ProductReleaseAssets } =
ImageProductSection;

const downloadsHtml = render(ProductDownloadsAndVersions, {
product: "server",
});

assert.ok(
downloadsHtml.includes("BLUEFIN SERVER"),
"server download card title rendered",
);
assert.ok(
downloadsHtml.includes("Download Installer"),
"installer action button rendered",
);
assert.ok(
downloadsHtml.includes("Download DDI"),
"ddi action button rendered",
);
assert.ok(
downloadsHtml.includes("Download UKI"),
"netboot UKI action button rendered",
);

const assetsHtml = render(ProductReleaseAssets, {
product: "server",
});

// Verify left-rail timeline markers
assert.ok(
assetsHtml.includes("Now"),
"archive rail Now label rendered for timeline",
);
assert.ok(
assetsHtml.includes("Past"),
"archive rail Past label rendered for timeline",
);

// Count rendered release cards for server (should be exactly 7)
const serverTagMatches = assetsHtml.match(/v26\.10\.\d+/g) || [];
const uniqueTags = [...new Set(serverTagMatches)];
assert.equal(
uniqueTags.length,
7,
"timeline displays exactly the last 7 releases",
);
});
14 changes: 11 additions & 3 deletions scripts/fetch-image-releases.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ function recentReleases(releases, source) {
const buckets = (families.length ? families : ["unknown"]).map(
(family) => `${family}:${stream}`,
);
const keep = buckets.some(
(bucket) => (counts.get(bucket) ?? 0) < PER_STREAM,
);
const limit =
source.repository === PRODUCT_SOURCES.server.repository ? 7 : PER_STREAM;
const keep = buckets.some((bucket) => (counts.get(bucket) ?? 0) < limit);
for (const bucket of buckets)
counts.set(bucket, (counts.get(bucket) ?? 0) + 1);
return keep;
Expand Down Expand Up @@ -533,6 +533,14 @@ if (
assert.equal(row.assets[0].size, 0);
assert.equal(row.assets[0].name, "SHA256SUMS.gpg");
assert.equal(row.versions, null);
const serverReleases = Array.from({ length: 10 }, (_, i) => ({
tag_name: `v26.10.${1200 + i}`,
draft: false,
}));
assert.equal(
recentReleases(serverReleases, PRODUCT_SOURCES.server).length,
7,
);
console.log("fetch-image-releases: metadata self-check passed");
} else if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
try {
Expand Down
6 changes: 6 additions & 0 deletions src/components/DownloadCard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
.cardDakota::after {
background-image: url("/img/characters/dakotaraptor.webp");
}
.cardServer::after {
background-image: url("/img/portal/characters/alamosaurus.webp");
}

/* ── Stream accent borders ── */

Expand All @@ -46,6 +49,9 @@
.cardDakota {
border-left: 3px solid #7c3aed;
}
.cardServer {
border-left: 3px solid #10b981;
}

/* ── Header ── */

Expand Down
6 changes: 4 additions & 2 deletions src/components/DownloadCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface DownloadEntry {
isoFilename: string;
torrentUrl?: string;
checksumUrl: string;
buttonLabel?: string;
}

export interface EntryGroup {
Expand All @@ -15,7 +16,7 @@ export interface EntryGroup {
}

export interface DownloadCardProps {
variant: "bluefin" | "dakotaraptor";
variant: "bluefin" | "dakotaraptor" | "server";
title: string;
description: React.ReactNode;
entries: DownloadEntry[];
Expand All @@ -28,6 +29,7 @@ export interface DownloadCardProps {
const VARIANT_CLASS: Record<DownloadCardProps["variant"], string> = {
bluefin: styles.cardBluefin,
dakotaraptor: styles.cardDakota,
server: styles.cardServer,
};

const LOGO_MAP: Record<string, { src: string; alt: string }> = {
Expand Down Expand Up @@ -83,7 +85,7 @@ function EntryRow({
download={entry.isoFilename}
className={styles.downloadButton}
>
📥 Download ISO
{entry.buttonLabel ? `📥 ${entry.buttonLabel}` : "📥 Download ISO"}
</a>
{entry.torrentUrl ? (
<a href={entry.torrentUrl} className={styles.secondaryLink}>
Expand Down
101 changes: 101 additions & 0 deletions src/components/ImageProductSection.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,104 @@
color: var(--ifm-color-emphasis-600);
font-style: italic;
}

.timelinePage {
position: relative;
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
padding-left: 2.35rem;
margin-top: 1.25rem;
}

.archiveRail {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 1.9rem;
pointer-events: none;
}

.archiveRail::before {
content: "";
position: absolute;
left: 0.8rem;
top: 1.05rem;
bottom: 1.05rem;
width: 6px;
border-radius: 999px;
background: linear-gradient(
to bottom,
#10b981 0%,
#059669 25%,
var(--ifm-color-primary) 50%,
color-mix(in srgb, #22d3ee 70%, var(--ifm-color-primary)) 75%,
color-mix(in srgb, #22d3ee 30%, transparent) 90%,
transparent 100%
);
box-shadow: 0 0 12px 2px color-mix(in srgb, #10b981 40%, transparent);
}

.archiveNow,
.archivePast {
position: absolute;
left: 0;
font-size: 0.66rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ifm-color-emphasis-700);
}

.archiveNow {
top: 0;
}

.archivePast {
bottom: 0;
}

.releaseDivider {
height: 2px;
margin: 0.4rem 0;
border-radius: 999px;
background: linear-gradient(
to right,
transparent 0%,
color-mix(in srgb, #10b981 40%, transparent) 10%,
color-mix(in srgb, #4a9eff 80%, transparent) 40%,
color-mix(in srgb, #4a9eff 95%, transparent) 50%,
color-mix(in srgb, #4a9eff 80%, transparent) 60%,
color-mix(in srgb, #10b981 40%, transparent) 90%,
transparent 100%
);
box-shadow: 0 0 8px 1px color-mix(in srgb, #10b981 25%, transparent);
}

[data-theme="dark"] .releaseDivider {
background: linear-gradient(
to right,
transparent 0%,
color-mix(in srgb, #34d399 35%, transparent) 10%,
color-mix(in srgb, #7ec8ff 70%, transparent) 40%,
color-mix(in srgb, #7ec8ff 90%, transparent) 50%,
color-mix(in srgb, #7ec8ff 70%, transparent) 60%,
color-mix(in srgb, #34d399 35%, transparent) 90%,
transparent 100%
);
box-shadow: 0 0 10px 1px color-mix(in srgb, #34d399 30%, transparent);
}

@media (max-width: 600px) {
.timelinePage {
padding-left: 1.75rem;
}
.archiveRail {
width: 1.4rem;
}
.archiveRail::before {
left: 0.55rem;
width: 5px;
}
}
Loading
Loading