diff --git a/.gitignore b/.gitignore index 11d386d91a..88141f36ac 100644 --- a/.gitignore +++ b/.gitignore @@ -65,7 +65,7 @@ homebrew-tap/ # never meant to be committed. .DONE improvements.md -docs/screenshots/ +# FNXC:RepoHygiene 2026-06-17-00:38: Published Markdown docs embed `docs/screenshots/*.png`; keep that directory trackable so GitHub, npm-rendered docs, and fresh clones do not show broken images. .claude/ # Local kb state and backups diff --git a/docs/screenshots/agents-view.png b/docs/screenshots/agents-view.png new file mode 100644 index 0000000000..9595b20952 Binary files /dev/null and b/docs/screenshots/agents-view.png differ diff --git a/docs/screenshots/chat-view.png b/docs/screenshots/chat-view.png new file mode 100644 index 0000000000..bed0177d1f Binary files /dev/null and b/docs/screenshots/chat-view.png differ diff --git a/docs/screenshots/dashboard-overview.png b/docs/screenshots/dashboard-overview.png new file mode 100644 index 0000000000..6dc6c65af8 Binary files /dev/null and b/docs/screenshots/dashboard-overview.png differ diff --git a/docs/screenshots/documents-view.png b/docs/screenshots/documents-view.png new file mode 100644 index 0000000000..eb80c36517 Binary files /dev/null and b/docs/screenshots/documents-view.png differ diff --git a/docs/screenshots/git-manager.png b/docs/screenshots/git-manager.png new file mode 100644 index 0000000000..4747ced14c Binary files /dev/null and b/docs/screenshots/git-manager.png differ diff --git a/docs/screenshots/list-view.png b/docs/screenshots/list-view.png new file mode 100644 index 0000000000..dd86a398d3 Binary files /dev/null and b/docs/screenshots/list-view.png differ diff --git a/docs/screenshots/mailbox-view.png b/docs/screenshots/mailbox-view.png new file mode 100644 index 0000000000..431ab34124 Binary files /dev/null and b/docs/screenshots/mailbox-view.png differ diff --git a/docs/screenshots/memory-view.png b/docs/screenshots/memory-view.png new file mode 100644 index 0000000000..055208e1b0 Binary files /dev/null and b/docs/screenshots/memory-view.png differ diff --git a/docs/screenshots/mission-manager.png b/docs/screenshots/mission-manager.png new file mode 100644 index 0000000000..a636e6db50 Binary files /dev/null and b/docs/screenshots/mission-manager.png differ diff --git a/docs/screenshots/nodes-view.png b/docs/screenshots/nodes-view.png new file mode 100644 index 0000000000..b694816faa Binary files /dev/null and b/docs/screenshots/nodes-view.png differ diff --git a/docs/screenshots/roadmaps-view.png b/docs/screenshots/roadmaps-view.png new file mode 100644 index 0000000000..bee73bc20a Binary files /dev/null and b/docs/screenshots/roadmaps-view.png differ diff --git a/docs/screenshots/settings.png b/docs/screenshots/settings.png new file mode 100644 index 0000000000..138f5cdf3a Binary files /dev/null and b/docs/screenshots/settings.png differ diff --git a/docs/screenshots/skills-view.png b/docs/screenshots/skills-view.png new file mode 100644 index 0000000000..9a0f3a4c38 Binary files /dev/null and b/docs/screenshots/skills-view.png differ diff --git a/docs/screenshots/task-detail.png b/docs/screenshots/task-detail.png new file mode 100644 index 0000000000..c487d2bec4 Binary files /dev/null and b/docs/screenshots/task-detail.png differ diff --git a/docs/screenshots/terminal.png b/docs/screenshots/terminal.png new file mode 100644 index 0000000000..3eaf5c6cbb Binary files /dev/null and b/docs/screenshots/terminal.png differ diff --git a/docs/screenshots/workflow-steps.png b/docs/screenshots/workflow-steps.png new file mode 100644 index 0000000000..2b5f95b056 Binary files /dev/null and b/docs/screenshots/workflow-steps.png differ diff --git a/packages/cli/src/__tests__/docs-screenshot-links.test.ts b/packages/cli/src/__tests__/docs-screenshot-links.test.ts new file mode 100644 index 0000000000..97adc882c4 --- /dev/null +++ b/packages/cli/src/__tests__/docs-screenshot-links.test.ts @@ -0,0 +1,96 @@ +import { execFileSync } from "node:child_process"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { dirname, relative, resolve, sep } from "node:path"; +import { describe, expect, it } from "vitest"; + +const workspaceRoot = resolve(import.meta.dirname, "../../../.."); +const docsRoot = resolve(workspaceRoot, "docs"); + +/* +FNXC:DocsScreenshots 2026-06-17-00:38: +Published docs render on GitHub and in fresh clones, so screenshot image references must resolve to committed files, not only developer-local files that happen to exist on disk. +Assert both filesystem presence and `git ls-files` tracking so a gitignored-but-present `docs/screenshots/` directory cannot regress silently. +*/ + +function collectMarkdownFiles(directory: string): string[] { + return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const entryPath = resolve(directory, entry.name); + if (entry.isDirectory()) { + return collectMarkdownFiles(entryPath); + } + if (entry.isFile() && entry.name.endsWith(".md")) { + return [entryPath]; + } + return []; + }); +} + +function toRepoRelativePath(absolutePath: string): string { + return relative(workspaceRoot, absolutePath).split(sep).join("/"); +} + +function gitTracks(relativePath: string): boolean { + const output = execFileSync("git", ["ls-files", "--", relativePath], { + cwd: workspaceRoot, + encoding: "utf8", + }).trim(); + return output.length > 0; +} + +describe("docs screenshot links", () => { + it("points every screenshot image reference at an existing tracked asset", () => { + const markdownFiles = [...collectMarkdownFiles(docsRoot), resolve(workspaceRoot, "README.md")]; + const screenshotReferences: Array<{ source: string; target: string; resolvedPath: string; repoPath: string }> = []; + + for (const markdownFile of markdownFiles) { + const markdown = readFileSync(markdownFile, "utf8"); + const imagePattern = /!\[[^\]]*\]\(([^)]+)\)/g; + for (const match of markdown.matchAll(imagePattern)) { + const rawTarget = match[1]?.trim().replace(/^<|>$/g, "") ?? ""; + const targetWithoutTitle = rawTarget.split(/\s+/)[0] ?? ""; + const targetWithoutFragment = targetWithoutTitle.replace(/[?#].*$/, ""); + if (!/(?:^|\/)screenshots\/[^/]+\.png$/i.test(targetWithoutFragment)) { + continue; + } + + const resolvedPath = resolve(dirname(markdownFile), targetWithoutFragment); + screenshotReferences.push({ + source: toRepoRelativePath(markdownFile), + target: targetWithoutTitle, + resolvedPath, + repoPath: toRepoRelativePath(resolvedPath), + }); + } + } + + expect(screenshotReferences.map(({ repoPath }) => repoPath).sort()).toEqual([ + "docs/screenshots/agents-view.png", + "docs/screenshots/chat-view.png", + "docs/screenshots/dashboard-overview.png", + "docs/screenshots/dashboard-overview.png", + "docs/screenshots/dashboard-overview.png", + "docs/screenshots/documents-view.png", + "docs/screenshots/git-manager.png", + "docs/screenshots/list-view.png", + "docs/screenshots/mailbox-view.png", + "docs/screenshots/memory-view.png", + "docs/screenshots/mission-manager.png", + "docs/screenshots/nodes-view.png", + "docs/screenshots/skills-view.png", + "docs/screenshots/task-detail.png", + "docs/screenshots/task-detail.png", + "docs/screenshots/terminal.png", + "docs/screenshots/workflow-steps.png", + ]); + + const missingFiles = screenshotReferences + .filter(({ resolvedPath }) => !existsSync(resolvedPath)) + .map(({ source, target, repoPath }) => `${source} -> ${target} (${repoPath})`); + const untrackedFiles = screenshotReferences + .filter(({ repoPath }) => !gitTracks(repoPath)) + .map(({ source, target, repoPath }) => `${source} -> ${target} (${repoPath})`); + + expect(missingFiles).toEqual([]); + expect(untrackedFiles).toEqual([]); + }); +});