From 127b2a0a902af8b7838b3d78959a5416f8f85cf0 Mon Sep 17 00:00:00 2001 From: Aryam Goyal Date: Sun, 2 Aug 2026 13:54:52 +0530 Subject: [PATCH 1/2] Fix vendored bundle detection --- benchmarks/adversarial/README.md | 9 +++-- benchmarks/adversarial/dataset.json | 8 ++++ benchmarks/adversarial/results.json | 19 +++++++++- packages/action/dist/index.mjs | 10 +++-- packages/core/src/rank.ts | 19 +++++++--- packages/core/test/rank.test.ts | 57 ++++++++++++++++++++++++++++- scripts/evaluate-adversarial.mjs | 47 ++++++++++++++++++++++-- 7 files changed, 150 insertions(+), 19 deletions(-) diff --git a/benchmarks/adversarial/README.md b/benchmarks/adversarial/README.md index 17028f9..1508566 100644 --- a/benchmarks/adversarial/README.md +++ b/benchmarks/adversarial/README.md @@ -10,11 +10,11 @@ A wrong answer costs an agent one wasted file read. A wrong answer delivered wit | Result | Value | | --- | ---: | -| Cases | 8 | -| Passed | 8 | +| Cases | 9 | +| Passed | 9 | | **False-confidence rate** | **0.0** | -Measured 2026-07-26. Per-case output is checked in at [`results.json`](results.json). +Measured 2026-08-02. Per-case output is checked in at [`results.json`](results.json). ## The cases @@ -28,8 +28,9 @@ Measured 2026-07-26. Per-case output is checked in at [`results.json`](results.j | `absent-feature-surface` | chalk | A CLI flag for a package that exposes no CLI | | `runtime-only-symptom` | pino | A timing-dependent concurrency symptom with no static trace | | `documentation-term-flood` | webpack | Only broad development vocabulary that instruction and documentation files carry in volume | +| `pretty-printed-vendored-bundle` | Synthetic compiled dependency | A readable development bundle with one source-map marker and no maintained source twin | -Cases reuse the checkouts already pinned by the accuracy suites, so this suite clones nothing new. +External cases reuse the checkouts already pinned by the accuracy suites, so this suite clones nothing new. The generated-output case uses a deterministic temporary fixture and removes it after every run. ## Why this is not only unit tests diff --git a/benchmarks/adversarial/dataset.json b/benchmarks/adversarial/dataset.json index 9c0ced5..7822b31 100644 --- a/benchmarks/adversarial/dataset.json +++ b/benchmarks/adversarial/dataset.json @@ -129,6 +129,14 @@ "descriptive", "vague" ] + }, + { + "id": "pretty-printed-vendored-bundle", + "kind": "generated-output", + "fixture": "pretty-printed-vendored-bundle", + "task": "experimental transition state", + "why": "A readable development bundle under compiled/ contains the task vocabulary and one source-map marker but has no maintained source twin. It must not lead at high confidence.", + "maxConfidence": "low" } ] } diff --git a/benchmarks/adversarial/results.json b/benchmarks/adversarial/results.json index 7f2fe57..dddb894 100644 --- a/benchmarks/adversarial/results.json +++ b/benchmarks/adversarial/results.json @@ -1,6 +1,6 @@ { - "cases": 8, - "passed": 8, + "cases": 9, + "passed": 9, "falseConfidenceRate": 0, "results": [ { @@ -132,6 +132,21 @@ "groundingOk": true, "diagnosticOk": true, "passed": true + }, + { + "id": "pretty-printed-vendored-bundle", + "kind": "generated-output", + "maxConfidence": "low", + "topConfidence": null, + "grounding": "descriptive", + "diagnostics": [ + "no-context-match" + ], + "contextFileCount": 0, + "overconfident": false, + "groundingOk": true, + "diagnosticOk": true, + "passed": true } ] } diff --git a/packages/action/dist/index.mjs b/packages/action/dist/index.mjs index 5f160be..18ca16a 100644 --- a/packages/action/dist/index.mjs +++ b/packages/action/dist/index.mjs @@ -1142,7 +1142,7 @@ function rankContextFiles(repo, input, limit = DEFAULT_CONTEXT_FILE_LIMIT, minSc score -= BACKUP_COPY_PENALTY; reasons.push("backup or archived copy deprioritized"); } - if (isBundledOutput(file.textSample) && !isChanged && !mentionedPaths.has(file.path)) { + if (isBundledOutput(file.textSample, file.path) && !isChanged && !mentionedPaths.has(file.path)) { score -= BUNDLED_OUTPUT_PENALTY; reasons.push("machine-generated bundle deprioritized"); } @@ -1347,7 +1347,7 @@ function findRegexTokenOverlap(text, taskTokens) { } return [...overlap].slice(0, 2); } -function isBundledOutput(textSample) { +function isBundledOutput(textSample, path) { if (textSample.length < MIN_BUNDLE_SAMPLE_BYTES) { return false; } @@ -1355,7 +1355,11 @@ function isBundledOutput(textSample) { if (textSample.length / lineCount >= BUNDLED_LINE_LENGTH) { return true; } - return BUNDLE_MARKERS.filter((marker) => marker.test(textSample)).length >= 2; + const markerCount = BUNDLE_MARKERS.filter((marker) => marker.test(textSample)).length; + return markerCount >= 2 || markerCount >= 1 && isConventionalBundlePath(path); +} +function isConventionalBundlePath(path) { + return isGeneratedPath(path) || path.split("/").slice(0, -1).some((segment) => segment.toLowerCase() === "compiled"); } function isTypeDeclarationPath(path) { return /\.d\.(?:ts|mts|cts)$/i.test(path); diff --git a/packages/core/src/rank.ts b/packages/core/src/rank.ts index a319768..e52aa20 100644 --- a/packages/core/src/rank.ts +++ b/packages/core/src/rank.ts @@ -307,7 +307,7 @@ export function rankContextFiles( reasons.push("backup or archived copy deprioritized"); } - if (isBundledOutput(file.textSample) && !isChanged && !mentionedPaths.has(file.path)) { + if (isBundledOutput(file.textSample, file.path) && !isChanged && !mentionedPaths.has(file.path)) { score -= BUNDLED_OUTPUT_PENALTY; reasons.push("machine-generated bundle deprioritized"); } @@ -599,7 +599,7 @@ function findRegexTokenOverlap(text: string, taskTokens: Set): string[] return [...overlap].slice(0, 2); } -function isBundledOutput(textSample: string): boolean { +function isBundledOutput(textSample: string, path: string): boolean { if (textSample.length < MIN_BUNDLE_SAMPLE_BYTES) { return false; } @@ -609,10 +609,17 @@ function isBundledOutput(textSample: string): boolean { } // Modern development bundles are often pretty-printed to a few dozen characters per - // line, so line length alone misses them. Two independent bundler fingerprints keep - // this conservative: readable vendored source with one helper-like identifier is not - // penalized, while webpack/esbuild runtime output is. - return BUNDLE_MARKERS.filter((marker) => marker.test(textSample)).length >= 2; + // line, so line length alone misses them. Two independent fingerprints identify one + // anywhere. A single fingerprint is enough only when the path supplies independent + // generated-output evidence. That catches Next.js-style `dist/compiled/` bundles while + // leaving ordinary readable vendored source, such as chalk's `source/vendor/`, alone. + const markerCount = BUNDLE_MARKERS.filter((marker) => marker.test(textSample)).length; + return markerCount >= 2 || (markerCount >= 1 && isConventionalBundlePath(path)); +} + +function isConventionalBundlePath(path: string): boolean { + return isGeneratedPath(path) || path.split("/").slice(0, -1) + .some((segment) => segment.toLowerCase() === "compiled"); } function isTypeDeclarationPath(path: string): boolean { diff --git a/packages/core/test/rank.test.ts b/packages/core/test/rank.test.ts index e81a39e..6d82457 100644 --- a/packages/core/test/rank.test.ts +++ b/packages/core/test/rank.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { rankContextFiles } from "../src/rank.js"; +import { rankContextFiles, REPORT_SCORE_CUTOFF } from "../src/rank.js"; import type { RepoMap } from "../src/types.js"; describe("rankContextFiles", () => { @@ -1210,6 +1210,61 @@ describe("rankContextFiles", () => { .toContain("machine-generated bundle deprioritized"); }); + it("deprioritizes a pretty-printed vendored bundle with one marker and no source twin", () => { + const bundle = [ + ...Array.from( + { length: 150 }, + (_, index) => + `function transitionState${index}() { return "experimental transition state"; }` + ), + "//# sourceMappingURL=react-dom.development.js.map" + ].join("\n"); + const repo: RepoMap = { + root: "/repo", + packageScripts: [], + changedFiles: [], + diffText: "", + packageManager: "npm", + diagnostics: [], + files: [ + { + path: "src/router/render.ts", + extension: ".ts", + sizeBytes: 120, + isSource: true, + isTest: false, + kind: "code", + textSample: "export function renderRoute() { return renderPage(); }" + }, + { + path: "compiled/react-dom/cjs/react-dom.development.js", + extension: ".js", + sizeBytes: bundle.length, + isSource: true, + isTest: false, + kind: "code", + textSample: bundle + } + ] + }; + + const ranked = rankContextFiles( + repo, + { issueText: "experimental transition state" }, + 8, + Number.NEGATIVE_INFINITY + ); + const vendoredBundle = ranked.find( + (file) => file.path === "compiled/react-dom/cjs/react-dom.development.js" + ); + + expect(bundle.length).toBeGreaterThan(2_000); + expect(bundle.length / bundle.split("\n").length).toBeLessThan(100); + expect(vendoredBundle?.score).toBeLessThan(REPORT_SCORE_CUTOFF); + expect(vendoredBundle?.confidence).toBe("low"); + expect(vendoredBundle?.reasons).toContain("machine-generated bundle deprioritized"); + }); + it("leaves readable vendored source alone, however long the file", () => { const vendored = Array.from( { length: 400 }, diff --git a/scripts/evaluate-adversarial.mjs b/scripts/evaluate-adversarial.mjs index 11e35de..951d8ad 100644 --- a/scripts/evaluate-adversarial.mjs +++ b/scripts/evaluate-adversarial.mjs @@ -16,9 +16,10 @@ // fixtures contain the fabricated identifiers, so running these against this // repository would resolve them and silently pass. -import { readFile, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { fileURLToPath, pathToFileURL } from "node:url"; import { dirname, join, resolve } from "node:path"; +import { tmpdir } from "node:os"; import { materializePinnedRepository } from "./lib/external-cache.mjs"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); @@ -40,8 +41,16 @@ const CHECKOUT_ENVIRONMENT_CODES = new Set(["tracked-paths-absent", "duplicate-r const results = []; for (const testCase of dataset.cases) { - const dir = await materializePinnedRepository(testCase); - const report = await buildFixMapReport({ repoRoot: dir, issueText: testCase.task }); + const fixtureDir = testCase.fixture ? await materializeFixture(testCase.fixture) : null; + const dir = fixtureDir ?? await materializePinnedRepository(testCase); + let report; + try { + report = await buildFixMapReport({ repoRoot: dir, issueText: testCase.task }); + } finally { + if (fixtureDir) { + await rm(fixtureDir, { recursive: true, force: true }); + } + } const topConfidence = report.contextFiles[0]?.confidence ?? null; const grounding = report.analysis?.grounding?.specificity ?? null; @@ -130,3 +139,35 @@ if (process.argv.includes("--gate") && failures.length > 0) { if (failed) { process.exit(1); } + +async function materializeFixture(name) { + if (name !== "pretty-printed-vendored-bundle") { + throw new Error(`Unknown adversarial fixture: ${name}`); + } + + const root = await mkdtemp(join(tmpdir(), "fixmap-adversarial-bundle-")); + try { + const compiledDir = join(root, "compiled", "react-dom", "cjs"); + const sourceDir = join(root, "src", "router"); + await mkdir(compiledDir, { recursive: true }); + await mkdir(sourceDir, { recursive: true }); + + const bundle = [ + ...Array.from( + { length: 150 }, + (_, index) => `function transitionState${index}() { return "experimental transition state"; }` + ), + "//# sourceMappingURL=react-dom.development.js.map" + ].join("\n"); + await writeFile(join(compiledDir, "react-dom.development.js"), bundle, "utf8"); + await writeFile( + join(sourceDir, "render.ts"), + "export function renderRoute() { return renderPage(); }\n", + "utf8" + ); + return root; + } catch (error) { + await rm(root, { recursive: true, force: true }); + throw error; + } +} From 79015d5f66f9866bdf437cc84bbe4ba545951d13 Mon Sep 17 00:00:00 2001 From: Aryam Goyal Date: Sun, 2 Aug 2026 14:16:45 +0530 Subject: [PATCH 2/2] Add website changelog --- apps/web/app/_components/site-footer.tsx | 2 +- apps/web/app/_components/site-header.tsx | 1 + apps/web/app/changelog/page.tsx | 261 +++++++++++++++++++++++ apps/web/app/globals.css | 43 ++++ apps/web/app/sitemap.ts | 2 +- 5 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 apps/web/app/changelog/page.tsx diff --git a/apps/web/app/_components/site-footer.tsx b/apps/web/app/_components/site-footer.tsx index c71cc1b..1feafcf 100644 --- a/apps/web/app/_components/site-footer.tsx +++ b/apps/web/app/_components/site-footer.tsx @@ -12,7 +12,7 @@ export function SiteFooter() {

A practical map before the first edit. Free, local-first, and open source.

-
ExploreProductLive demoEvidence
+
ExploreProductLive demoEvidenceChangelog
Use FixMapGet startedDocsGitHub Action
Project GitHubnpmIssues
diff --git a/apps/web/app/_components/site-header.tsx b/apps/web/app/_components/site-header.tsx index 28ac016..1706bde 100644 --- a/apps/web/app/_components/site-header.tsx +++ b/apps/web/app/_components/site-header.tsx @@ -7,6 +7,7 @@ const links = [ { href: "/product", label: "Product" }, { href: "/demo", label: "Live demo" }, { href: "/evidence", label: "Evidence" }, + { href: "/changelog", label: "Changelog" }, { href: "/docs", label: "Docs" } ]; diff --git a/apps/web/app/changelog/page.tsx b/apps/web/app/changelog/page.tsx new file mode 100644 index 0000000..abade12 --- /dev/null +++ b/apps/web/app/changelog/page.tsx @@ -0,0 +1,261 @@ +import type { Metadata } from "next"; +import { + ArrowRight, + ChartLineUp, + CheckCircle, + Plus, + ShieldCheck, + Sparkle, + Wrench +} from "@phosphor-icons/react/ssr"; +import { repoUrl, siteStats } from "../_lib/site-data"; + +export const metadata: Metadata = { + title: "Changelog", + description: "See what is new, improved, and fixed in every recent FixMap release.", + alternates: { canonical: "/changelog" } +}; + +type ChangeGroup = { + label: "Added" | "Fixed" | "Improved" | "Security" | "Evidence"; + items: string[]; +}; + +type Release = { + version: string; + date: string; + label?: string; + summary: string; + groups: ChangeGroup[]; +}; + +const releases: Release[] = [ + { + version: "Unreleased", + date: "Pending release", + label: "In progress", + summary: "Improvements that are tested and ready for the next package release.", + groups: [ + { + label: "Fixed", + items: [ + "Pretty-printed vendored dependency bundles with a source-map marker no longer escape generated-output detection or rank as a high-confidence edit target." + ] + }, + { + label: "Evidence", + items: [ + "Added a dedicated adversarial fixture for readable compiled dependencies while keeping Chalk's real vendored implementation ranked first.", + "All 420 workspace tests pass; held-out and external accuracy remain unchanged, and the adversarial suite is now 9/9 with zero false-confidence cases." + ] + } + ] + }, + { + version: "0.8.6", + date: "August 2, 2026", + label: "Latest release", + summary: "Cleaner implementation rankings without hiding legitimate UI or generated-artifact work.", + groups: [ + { + label: "Fixed", + items: [ + "Stylesheets are deprioritized for non-UI implementation tasks, while genuine CSS and layout tasks still rank them normally.", + "Explicitly named generated artifacts remain visible, but a maintained source twin caps their confidence and explains the source relationship." + ] + }, + { + label: "Evidence", + items: [ + "Added counterexample coverage for real CSS tasks and tasks that genuinely target a stale generated artifact.", + "Held-out accuracy remained 7/12 Top-1, 8/12 Top-3, and 9/12 Top-5." + ] + } + ] + }, + { + version: "0.8.5", + date: "August 2, 2026", + summary: "A proper installation path and a much shorter everyday command.", + groups: [ + { + label: "Improved", + items: [ + "The README and website now lead with a global installation followed by the short fixmap command.", + "Install guidance now explains when npm may prefer an older project-local binary and why the running version printed by Doctor is authoritative." + ] + } + ] + }, + { + version: "0.8.4", + date: "August 2, 2026", + summary: "Doctor can identify an exact-version request that started the wrong installation.", + groups: [ + { + label: "Fixed", + items: [ + "fixmap doctor reports both the requested and running versions when a local or ancestor installation shadows an exact npm request.", + "The clean verification procedure uses an isolated npm prefix and invokes its shim directly on Windows." + ] + } + ] + }, + { + version: "0.8.3", + date: "August 2, 2026", + summary: "Stricter MCP comparison inputs, with honest failures instead of false success.", + groups: [ + { + label: "Fixed", + items: [ + "fixmap_compare rejects truncated report-shaped objects while continuing to accept complete reports that legitimately contain zero matches.", + "Optional rank, score, and confidence fields are type-checked when present." + ] + } + ] + }, + { + version: "0.8.2", + date: "August 2, 2026", + summary: "A broad reliability release across Windows, test routing, MCP, the Action, and diagnostics.", + groups: [ + { + label: "Added", + items: [ + "New diagnostics identify unread content, missing tracked paths, duplicate real paths, generated-path dominance, and missing related tests.", + "MCP explain gained working-tree controls, the product page gained Compare, and the site gained robots.txt." + ] + }, + { + label: "Fixed", + items: [ + "Windows path normalization, BOM and UTF-16 manifests, common GitHub URL forms, failed diff handling, and additional source languages now behave consistently.", + "Workspace test routing, Action exclusions and comments, and stemming were hardened with regression coverage." + ] + } + ] + }, + { + version: "0.8.1", + date: "August 1, 2026", + summary: "The first large dogfooding sweep closed gaps across every FixMap surface.", + groups: [ + { + label: "Added", + items: [ + "Compare and Doctor MCP tools, working-tree controls, browser-safe exports, and Action inputs for limit, exclude, and untracked files.", + "Release gates now verify the npm latest tag, canonical homepage metadata, internal versions, and a clean installation before publishing." + ] + }, + { + label: "Fixed", + items: [ + "CLI validation, comparison output, exclusions, confidence, risk evidence, JSON ranks, test routing, and Action comment selection." + ] + } + ] + }, + { + version: "0.8.0", + date: "August 1, 2026", + summary: "Plan, focus, compare, and verify became one coherent workflow.", + groups: [ + { + label: "Added", + items: [ + "Go and Rust test routing, fixmap doctor, plan comparison, exclusions, result limits, working-tree mode, progress phases, pull-request URLs, and Action verify mode.", + "MCP explain lets agents investigate a missing file without shell access." + ] + }, + { + label: "Fixed", + items: [ + "Confidence now reflects real ranking separation instead of labeling a whole result page high.", + "Language detection, diagnostic bounds, file-mention performance, duplicate flags, verify output, and report consistency were corrected." + ] + }, + { + label: "Security", + items: [ + "Unbounded user text no longer flows into JSON reports, CI logs, or pull-request comments." + ] + } + ] + } +]; + +const icons = { + Added: Plus, + Fixed: Wrench, + Improved: Sparkle, + Security: ShieldCheck, + Evidence: ChartLineUp +} as const; + +const releaseId = (version: string) => `v-${version.toLowerCase().replaceAll(".", "-")}`; + +export default function ChangelogPage() { + return ( +
+
+

Product changelog

+

Every improvement.
Plainly recorded.

+

New features, important fixes, and the evidence behind each recent FixMap release.

+
+ Current package: v{siteStats.version} + Read the complete history +
+
+ +
+ + +
+ {releases.map((release) => ( +
+
+
+

{release.date}

+

{release.version === "Unreleased" ? "Coming next" : `FixMap v${release.version}`}

+
+ {release.label ? {release.label} : null} +
+

{release.summary}

+
+ {release.groups.map((group) => { + const Icon = icons[group.label]; + return ( +
+

{group.label}

+
    + {group.items.map((item) =>
  • {item}
  • )} +
+
+ ); + })} +
+
+ ))} +
+
+ +
+
+

Nothing hidden

Every release stays inspectable.

The repository contains the full history, exact test evidence, and source for every change.

+ Browse GitHub releases +
+
+
+ ); +} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 852ee3c..cc9ef71 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -313,6 +313,40 @@ h3 { margin-bottom: 10px; font-size: 21px; line-height: 1.25; letter-spacing: -. .docs-bottom > div:first-child p { color: #c2ccd4; } .not-found { min-height: 630px; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; } +/* Changelog */ +.changelog-hero { padding-bottom: 84px; } +.changelog-hero > p:not(.eyebrow) { max-width: 680px; } +.changelog-hero-meta { margin-top: 34px; display: flex; flex-wrap: wrap; align-items: center; gap: 18px 32px; } +.changelog-hero-meta > span { display: inline-flex; align-items: center; gap: 8px; color: var(--green-dark); font-size: 14px; font-weight: 650; } +.changelog-hero-meta > span svg { color: var(--green); } +.changelog-layout { padding-top: 90px; display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: clamp(60px, 8vw, 120px); align-items: start; } +.release-index { position: sticky; top: 116px; padding: 22px; border: 1px solid var(--line); border-radius: 12px; background: rgba(255,253,248,.72); } +.release-index > strong { color: var(--ink-faint); font: 650 11px var(--font-mono), monospace; text-transform: uppercase; letter-spacing: .1em; } +.release-index nav { margin-top: 15px; display: grid; } +.release-index a { padding: 11px 0; display: grid; gap: 2px; border-top: 1px solid var(--line); color: var(--ink); text-decoration: none; } +.release-index a:hover span { color: var(--green); } +.release-index span { font-size: 14px; font-weight: 650; transition: color .18s ease; } +.release-index small { color: var(--ink-faint); font-size: 11px; } +.release-list { position: relative; } +.release-list::before { content: ""; position: absolute; left: 7px; top: 9px; bottom: 72px; width: 1px; background: var(--line-strong); } +.release-entry { position: relative; padding: 0 0 86px 52px; scroll-margin-top: 120px; } +.release-entry::before { content: ""; position: absolute; left: 0; top: 8px; width: 15px; height: 15px; border: 3px solid var(--paper); border-radius: 50%; background: var(--green); box-shadow: 0 0 0 1px var(--green); } +.release-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 28px; } +.release-heading p { margin: 0 0 8px; color: var(--ink-faint); font: 600 11px var(--font-mono), monospace; text-transform: uppercase; letter-spacing: .08em; } +.release-heading h2 { font-size: clamp(34px, 4vw, 48px); } +.release-heading > span { flex: 0 0 auto; padding: 7px 10px; border: 1px solid rgba(10,90,67,.18); border-radius: 999px; background: var(--mint-soft); color: var(--green-dark); font: 650 10px var(--font-mono), monospace; text-transform: uppercase; letter-spacing: .06em; } +.release-summary { max-width: 720px; margin-top: 18px; color: var(--ink-soft); font-size: 18px; } +.release-groups { margin-top: 36px; border-bottom: 1px solid var(--line); } +.release-group { padding: 25px 0; display: grid; grid-template-columns: 145px minmax(0, 1fr); gap: 32px; border-top: 1px solid var(--line); } +.release-group h3 { display: flex; align-items: center; gap: 9px; color: var(--green-dark); font-size: 14px; } +.release-group h3 svg { color: var(--green); } +.release-group ul { margin: 0; padding: 0; display: grid; gap: 13px; list-style: none; } +.release-group li { position: relative; padding-left: 19px; color: var(--ink-soft); line-height: 1.65; } +.release-group li::before { content: ""; position: absolute; left: 0; top: .72em; width: 6px; height: 6px; border-radius: 50%; background: var(--mint); } +.changelog-bottom { display: flex; align-items: center; justify-content: space-between; gap: 60px; } +.changelog-bottom > div { max-width: 690px; } +.changelog-bottom > div > p:last-child { color: #b7c5d1; } + /* Footer */ .site-footer { padding: 72px max(32px, calc((100vw - var(--page)) / 2)); background: var(--navy); color: var(--paper-light); } .footer-main { display: grid; grid-template-columns: 1fr 1.2fr; gap: 100px; } @@ -370,6 +404,10 @@ h3 { margin-bottom: 10px; font-size: 21px; line-height: 1.25; letter-spacing: -. .setup-section { grid-template-columns: minmax(0, 1fr); gap: 34px; } .docs-layout { grid-template-columns: 1fr; } .docs-sidebar { display: none; } + .changelog-layout { grid-template-columns: 1fr; gap: 55px; } + .release-index { position: static; } + .release-index nav { grid-template-columns: repeat(4, 1fr); gap: 0 18px; } + .changelog-bottom { align-items: flex-start; flex-direction: column; } .footer-main { grid-template-columns: 1fr; gap: 54px; } } @@ -417,6 +455,11 @@ h3 { margin-bottom: 10px; font-size: 21px; line-height: 1.25; letter-spacing: -. .copy-command button { width: 100%; } .docs-cards { grid-template-columns: 1fr; } .definition-list > div { grid-template-columns: 1fr; gap: 6px; } + .changelog-hero-meta { align-items: flex-start; flex-direction: column; } + .release-index nav { grid-template-columns: 1fr 1fr; } + .release-entry { padding-left: 32px; } + .release-heading { flex-direction: column; gap: 14px; } + .release-group { grid-template-columns: 1fr; gap: 16px; } .setup-next { align-items: flex-start; flex-direction: column; } .footer-links { grid-template-columns: 1fr 1fr; } .footer-bottom { flex-direction: column; gap: 8px; } diff --git a/apps/web/app/sitemap.ts b/apps/web/app/sitemap.ts index 61f3a79..4a93a4b 100644 --- a/apps/web/app/sitemap.ts +++ b/apps/web/app/sitemap.ts @@ -3,7 +3,7 @@ import type { MetadataRoute } from "next"; const base = "https://usefixmap.vercel.app"; export default function sitemap(): MetadataRoute.Sitemap { - return ["", "/product", "/demo", "/evidence", "/get-started", "/docs"].map((path) => ({ + return ["", "/product", "/demo", "/evidence", "/changelog", "/get-started", "/docs"].map((path) => ({ url: `${base}${path}`, changeFrequency: path === "" ? "weekly" : "monthly", priority: path === "" ? 1 : path === "/demo" || path === "/get-started" ? 0.9 : 0.8