diff --git a/.codex/skills/what-i-want-to-exist-projects/SKILL.md b/.codex/skills/what-i-want-to-exist-projects/SKILL.md new file mode 100644 index 00000000..00e6fa64 --- /dev/null +++ b/.codex/skills/what-i-want-to-exist-projects/SKILL.md @@ -0,0 +1,61 @@ +--- +name: what-i-want-to-exist-projects +description: Use when adding, updating, or reviewing entries for the schalkneethling.com "What I want to exist" projects area, including project cards, project detail pages, repo doc cache refreshes, GOAL.md/ROADMAP.md links, and the little demos section. +--- + +# What I Want To Exist Projects + +Use this skill for changes under `src/content/projects/`, `src/pages/projects*`, +`src/components/Projects/`, `src/data/project-doc-cache/`, or tests that protect +the curated project listing and detail pages. + +## Workflow + +1. Read the current project entry or nearby entries in `src/content/projects/`. +2. If adding or materially updating a project, inspect the repo, cached docs, and + public project page context before writing summaries. +3. Keep public page copy editorial: summarize `GOAL.md`, `ROADMAP.md`, issues, + and repo docs instead of pasting long verbatim source text. +4. Preserve the current card/detail conventions: + - `/projects` remains the listing URL. + - Cards link to internal `/projects/{slug}` pages. + - Every card needs an `imageUrl`. + - `category: "main"` appears in the Projects section. + - `category: "demo"` appears in Little demos. +5. Update tests before implementation when behavior or required content changes. +6. Run the narrow relevant tests first, then the site checks listed below. + +## Adding Or Updating A Project + +For schema details and a frontmatter checklist, read +`references/project-entry.md`. + +Use the project slug as the filename in `src/content/projects/{slug}.md`. +Keep ordering deliberate. Do not automate project ordering unless the user asks. + +If repo docs changed, run: + +```sh +pnpm run refresh:project-docs +``` + +Review the generated cache diff under `src/data/project-doc-cache/`. The cache +is source material for editorial summaries, not content rendered directly into +the public pages. + +## Validation + +Run the smallest meaningful test first. Common commands: + +```sh +pnpm exec vitest run tests/projectContent.test.ts +pnpm exec vitest run tests/projectFilters.test.ts tests/projectPages.test.ts +pnpm exec vitest run tests/projectDocCache.test.ts +pnpm exec vitest run tests/projectSkill.test.ts +pnpm run typecheck +pnpm run build +pnpm run test:a11y +``` + +For listing or detail page UI changes, include the relevant Playwright coverage +in `tests/projects.spec.ts` and verify accessibility. diff --git a/.codex/skills/what-i-want-to-exist-projects/agents/openai.yaml b/.codex/skills/what-i-want-to-exist-projects/agents/openai.yaml new file mode 100644 index 00000000..e770236a --- /dev/null +++ b/.codex/skills/what-i-want-to-exist-projects/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "What I Want To Exist Projects" + short_description: "Maintain curated project pages" + default_prompt: "Use $what-i-want-to-exist-projects to add or update a project page." diff --git a/.codex/skills/what-i-want-to-exist-projects/references/project-entry.md b/.codex/skills/what-i-want-to-exist-projects/references/project-entry.md new file mode 100644 index 00000000..14f92969 --- /dev/null +++ b/.codex/skills/what-i-want-to-exist-projects/references/project-entry.md @@ -0,0 +1,41 @@ +# Project Entry Reference + +Project entries live in `src/content/projects/{slug}.md`. + +## Required Frontmatter + +- `title`: Display name. +- `description`: Short card summary. +- `category`: `"main"` for Projects or `"demo"` for Little demos. +- `order`: Integer used for deliberate listing order. +- `repoUrl`: GitHub repository URL. +- `imageUrl`: Card/detail visual. Prefer the repo OpenGraph image when present. + +## Optional Frontmatter + +- `language`: Primary language shown on cards/detail facts. +- `stars`: GitHub star count shown on cards. +- `liveUrl`: Live site or demo URL when available. +- `goalDocUrl`: Public GitHub link to `GOAL.md` when available. +- `roadmapDocUrl`: Public GitHub link to `ROADMAP.md` when available. +- `technologies`: Short list of project technologies. +- `whatAndWhy`: Editorial "what and why" detail-page summary. +- `goalSummary`: Editorial summary of the project goal. +- `currentState`: Where the project is now. +- `nextSteps`: Short list of what comes next. +- `contributionGuidance`: How people can help. + +## Content Rules + +- Use editorial summaries, not pasted repo docs. +- Keep summaries concrete and written in the site’s voice. +- Prefer links to `GOAL.md` and `ROADMAP.md` over duplicating full documents. +- Make missing docs visible only when useful; do not invent links. +- Keep detail content complete for `category: "main"` starter-style projects. + +## Tests To Consider + +- `tests/projectContent.test.ts`: required project fields, doc links, live URLs. +- `tests/projectFilters.test.ts`: category grouping and ordering. +- `tests/projectPages.test.ts`: internal project URL helpers. +- `tests/projects.spec.ts`: listing/detail rendering and accessibility-related UI. diff --git a/package.json b/package.json index 30033fa4..83c03cb1 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "setup:playwright": "pnpm exec playwright install chromium", "build": "pnpm run generate:og && astro build", "generate:og": "node scripts/generate-og-cards.mjs", + "refresh:project-docs": "node scripts/refresh-project-doc-cache.mjs", "preview": "astro preview", "astro": "astro", "typecheck": "astro check", diff --git a/playwright.config.ts b/playwright.config.ts index 854ffc14..3b1bfd9e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -75,6 +75,9 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { command: "pnpm run dev --host 127.0.0.1", + env: { + ASTRO_DEV_BACKGROUND: "1", + }, url: "http://localhost:4321", reuseExistingServer: !process.env.CI, }, diff --git a/scripts/refresh-project-doc-cache.mjs b/scripts/refresh-project-doc-cache.mjs new file mode 100644 index 00000000..33c5bcf5 --- /dev/null +++ b/scripts/refresh-project-doc-cache.mjs @@ -0,0 +1,223 @@ +import { mkdir, readdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const DEFAULT_CONTENT_DIRECTORY = "src/content/projects"; +const DEFAULT_CACHE_DIRECTORY = "src/data/project-doc-cache"; +const DEFAULT_BRANCH = "main"; +const DOC_BRANCHES = [DEFAULT_BRANCH, "master"]; +const FETCH_TIMEOUT_MS = 10_000; + +const DOCS = [ + { key: "goal", filename: "GOAL.md" }, + { key: "roadmap", filename: "ROADMAP.md" }, +]; + +function parseFrontmatter(contents) { + const match = contents.match(/^---\n(?[\s\S]*?)\n---/); + + if (!match?.groups?.frontmatter) { + return {}; + } + + return Object.fromEntries( + match.groups.frontmatter + .split("\n") + .map((line) => + line.match(/^(?[A-Za-z][A-Za-z0-9]*):\s+"?(?[^"]+)"?\s*$/), + ) + .filter((match) => match?.groups) + .map((match) => [match.groups.key, match.groups.value]), + ); +} + +function getErrorMessage(error) { + return error instanceof Error ? error.message : String(error); +} + +export function repoUrlToRawDocUrl( + repoUrl, + filename, + branch = DEFAULT_BRANCH, +) { + const url = new URL(repoUrl); + + if (url.hostname !== "github.com") { + throw new Error(`Unsupported repository host: ${url.hostname}`); + } + + const [owner, repo] = url.pathname.replace(/^\/|\/$/g, "").split("/"); + + if (!owner || !repo) { + throw new Error(`Invalid GitHub repository URL: ${repoUrl}`); + } + + return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filename}`; +} + +async function readProjectEntries(contentDirectory) { + const filenames = await readdir(contentDirectory); + const markdownFiles = filenames + .filter((filename) => filename.endsWith(".md")) + .sort((a, b) => a.localeCompare(b)); + + const projects = []; + + for (const filename of markdownFiles) { + const id = path.basename(filename, ".md"); + const contents = await readFile(path.join(contentDirectory, filename), "utf8"); + const frontmatter = parseFrontmatter(contents); + + if (!frontmatter.repoUrl) { + continue; + } + + projects.push({ + id, + repoUrl: frontmatter.repoUrl, + title: frontmatter.title ?? id, + }); + } + + return projects; +} + +async function fetchDoc(project, doc, fetchImpl) { + let sourceUrl = ""; + + try { + let missingResult; + + for (const branch of DOC_BRANCHES) { + sourceUrl = repoUrlToRawDocUrl(project.repoUrl, doc.filename, branch); + const response = await fetchImpl(sourceUrl, { + signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), + }); + + if (response.status === 404) { + missingResult = { + filename: doc.filename, + sourceUrl, + status: "missing", + statusCode: response.status, + content: "", + }; + continue; + } + + if (!response.ok) { + return { + filename: doc.filename, + sourceUrl, + status: "failed", + statusCode: response.status, + content: "", + error: `GitHub returned ${response.status}`, + }; + } + + return { + filename: doc.filename, + sourceUrl, + status: "fetched", + statusCode: response.status, + content: await response.text(), + }; + } + + return missingResult; + } catch (error) { + return { + filename: doc.filename, + sourceUrl, + status: "failed", + content: "", + error: getErrorMessage(error), + }; + } +} + +function updateSummary(summary, docResult) { + if (docResult.status === "fetched") { + summary.fetched += 1; + } else if (docResult.status === "missing") { + summary.missing += 1; + } else { + summary.failed += 1; + } +} + +export async function refreshProjectDocCache({ + contentDirectory = DEFAULT_CONTENT_DIRECTORY, + cacheDirectory = DEFAULT_CACHE_DIRECTORY, + fetch = globalThis.fetch, + now = new Date(), +} = {}) { + if (!fetch) { + throw new Error("A fetch implementation is required"); + } + + const projects = await readProjectEntries(contentDirectory); + const refreshedAt = now.toISOString(); + const summary = { + projects: projects.length, + fetched: 0, + missing: 0, + failed: 0, + }; + + await mkdir(cacheDirectory, { recursive: true }); + + for (const project of projects) { + const docs = {}; + + for (const doc of DOCS) { + const docResult = await fetchDoc(project, doc, fetch); + docs[doc.key] = docResult; + updateSummary(summary, docResult); + } + + await writeFile( + path.join(cacheDirectory, `${project.id}.json`), + `${JSON.stringify( + { + id: project.id, + title: project.title, + repoUrl: project.repoUrl, + refreshedAt, + docs, + }, + null, + 2, + )}\n`, + ); + } + + return summary; +} + +async function runCli() { + const summary = await refreshProjectDocCache(); + + console.log( + [ + `Refreshed project doc cache for ${summary.projects} projects.`, + `Fetched: ${summary.fetched}.`, + `Missing: ${summary.missing}.`, + `Failed: ${summary.failed}.`, + ].join(" "), + ); + + if (summary.failed > 0) { + process.exitCode = 1; + } +} + +const currentFileUrl = pathToFileURL(fileURLToPath(import.meta.url)).href; +const executedFileUrl = process.argv[1] + ? pathToFileURL(path.resolve(process.argv[1])).href + : ""; + +if (currentFileUrl === executedFileUrl) { + await runCli(); +} diff --git a/src/components/Header/Navigation.astro b/src/components/Header/Navigation.astro index 861d944f..c310721c 100644 --- a/src/components/Header/Navigation.astro +++ b/src/components/Header/Navigation.astro @@ -3,7 +3,7 @@
  • Return home
  • What I'm doing now
  • #whoami
  • -
  • Things I build
  • +
  • What I want to exist
  • My writing
  • Get in touch
  • diff --git a/src/components/Projects/ProjectCard.astro b/src/components/Projects/ProjectCard.astro index 859a3ee1..a0066386 100644 --- a/src/components/Projects/ProjectCard.astro +++ b/src/components/Projects/ProjectCard.astro @@ -7,9 +7,10 @@ interface Props { stars: number; imageUrl: string; featured?: boolean; + linkLabel?: string; } -const { name, description, url, language, stars, imageUrl, featured } = +const { name, description, url, language, stars, imageUrl, featured, linkLabel } = Astro.props; --- @@ -46,7 +47,7 @@ const { name, description, url, language, stars, imageUrl, featured } = - View on GitHub + {linkLabel ?? "View on GitHub"} @@ -110,14 +111,21 @@ const { name, description, url, language, stars, imageUrl, featured } = color: var(--color-text-muted); display: flex; font-size: var(--typography-sm-font-size); - gap: var(--size-16); + flex-wrap: wrap; + gap: var(--size-8) var(--size-16); margin-block-end: var(--size-16); } - .project-card-language { + .project-card-language, + .project-card-stars { align-items: center; - display: flex; - gap: var(--size-4); + display: inline-flex; + gap: var(--size-6); + line-height: 1; + } + + .project-card-language { + color: var(--color-text-muted); } .language-dot { @@ -133,27 +141,39 @@ const { name, description, url, language, stars, imageUrl, featured } = } .project-card-link { + align-items: center; + border: var(--size-1) solid var(--color-border); + border-radius: var(--border-radius-default); color: var(--color-link); + display: inline-flex; font-size: var(--typography-default-font-size); + font-weight: 700; + gap: var(--size-6); + padding-block: var(--size-4); + padding-inline: var(--size-12); position: relative; text-decoration: none; width: fit-content; + transition: + background-color 0.2s ease, + border-color 0.2s ease, + color 0.2s ease; } - .project-card-link::after { - background-color: var(--color-accent); - block-size: var(--size-2); - content: ""; - inline-size: 0; - inset-block-end: 0; - inset-inline-start: 0; - position: absolute; - transition: inline-size 0.2s ease; + .project-card-link span { + transition: transform 0.2s ease; } - .project-card-link:hover::after, - .project-card-link:focus::after { - inline-size: 100%; + .project-card-link:hover, + .project-card-link:focus { + background-color: var(--color-surface); + border-color: var(--color-accent); + color: var(--color-accent); + } + + .project-card-link:hover span, + .project-card-link:focus span { + transform: translateX(var(--size-2)); } .project-card-link:focus-visible { diff --git a/src/components/Projects/ProjectList.astro b/src/components/Projects/ProjectList.astro new file mode 100644 index 00000000..038cad53 --- /dev/null +++ b/src/components/Projects/ProjectList.astro @@ -0,0 +1,57 @@ +--- +import type { CollectionEntry } from "astro:content"; + +import ProjectCard from "./ProjectCard.astro"; +import { getProjectPath } from "../../lib/projectPages"; + +interface Props { + heading: string; + headingId: string; + intro?: string; + minColumnWidth?: number; + projects: CollectionEntry<"projects">[]; +} + +const { heading, headingId, intro, minColumnWidth = 280, projects } = + Astro.props; +--- + +
    +

    {heading}

    + {intro &&

    {intro}

    } + + { + projects.map((project) => ( + + )) + } + +
    + + diff --git a/src/content.config.ts b/src/content.config.ts index bb708066..1f022c3c 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -17,6 +17,33 @@ const postsCollection = defineCollection({ }), }); +const projectsCollection = defineCollection({ + loader: glob({ + pattern: "**/*.md", + base: "./src/content/projects", + }), + schema: z.object({ + category: z.enum(["main", "demo"]), + description: z.string(), + imageUrl: z.string().optional(), + language: z.string().optional(), + liveUrl: z.string().url().optional(), + order: z.number().int().positive(), + repoUrl: z.string().url(), + contributionGuidance: z.string().optional(), + currentState: z.string().optional(), + goalDocUrl: z.string().url().optional(), + goalSummary: z.string().optional(), + nextSteps: z.array(z.string()).optional(), + roadmapDocUrl: z.string().url().optional(), + stars: z.number().int().nonnegative().optional(), + technologies: z.array(z.string()).optional(), + title: z.string(), + whatAndWhy: z.string().optional(), + }), +}); + export const collections = { posts: postsCollection, + projects: projectsCollection, }; diff --git a/src/content/drafts/throwing-confetti-with-css-random.mdx b/src/content/drafts/throwing-confetti-with-css-random.mdx new file mode 100644 index 00000000..7feaf858 --- /dev/null +++ b/src/content/drafts/throwing-confetti-with-css-random.mdx @@ -0,0 +1,7 @@ +--- +title: "Throwing Confetti with CSS Random" +pubDate: 2026-07-07 +description: "" +author: "Schalk Neethling" +tags: ["css", "frontend-engineering-explained"] +--- diff --git a/src/content/projects/common-components.md b/src/content/projects/common-components.md new file mode 100644 index 00000000..ffcb497f --- /dev/null +++ b/src/content/projects/common-components.md @@ -0,0 +1,22 @@ +--- +title: "common-components" +description: "A collection of commonly used components as either HTML or Web Components." +category: "main" +order: 10 +repoUrl: "https://github.com/schalkneethling/common-components" +imageUrl: "https://opengraph.githubassets.com/bbda519087a7bfd65cf5cb287d564c489f6005c2993c92bfb3103aff2c288aa2/schalkneethling/common-components" +liveUrl: "https://schalkneethling.github.io/common-components/" +language: "HTML" +stars: 3 +technologies: ["HTML", "Web Components", "CSS"] +goalDocUrl: "https://github.com/schalkneethling/common-components/blob/main/GOAL.md" +roadmapDocUrl: "https://github.com/schalkneethling/common-components/blob/main/ROADMAP.md" +whatAndWhy: "A component catalog evolving into Fiori: a practical library of accessible, platform-first web components and experiments. It exists both as a useful component collection and as a real project where sibling tools can prove themselves." +goalSummary: "Make real, usable web platform components that people can copy, study, adapt, and use, while clearly documenting readiness, accessibility behavior, browser support, dependencies, and modern platform feature requirements." +currentState: "The repo has a Vite-powered catalog, multiple component demos, a React interoperability app, and a roadmap that treats alertbox as the first reference-quality component." +nextSteps: + - "Finish the pnpm and documentation turnaround so the project is coherent from a fresh checkout." + - "Add component maturity and support-status tables to separate production candidates from experiments." + - "Use alertbox to establish the documentation, testing, and accessibility bar for future components." +contributionGuidance: "Contributions should improve component clarity, accessibility, tests, and documentation. Label experiments honestly and keep platform components as the source of truth even when React examples are added." +--- diff --git a/src/content/projects/create-project-calavera.md b/src/content/projects/create-project-calavera.md new file mode 100644 index 00000000..10d2e50e --- /dev/null +++ b/src/content/projects/create-project-calavera.md @@ -0,0 +1,21 @@ +--- +title: "create-project-calavera" +description: "A simple starting skeleton of linters and formatters for common web projects." +category: "main" +order: 7 +repoUrl: "https://github.com/schalkneethling/create-project-calavera" +imageUrl: "https://repository-images.githubusercontent.com/903176922/d1319ba4-0fc9-4df5-8c96-f8c59c9be799" +liveUrl: "https://calavera.schalkneethling.com" +language: "JavaScript" +stars: 7 +technologies: ["JavaScript", "CLI", "Project Scaffolding"] +goalDocUrl: "https://github.com/schalkneethling/create-project-calavera/blob/main/GOAL.md" +whatAndWhy: "A recipe-driven tooling assistant for web projects. It exists to make linting, formatting, type-checking, CSS quality, and integration choices repeatable without turning Calavera into an application framework or starter kit." +goalSummary: "Help developers compose, apply, inspect, and refresh project tooling recipes through a CLI and web composer, with checked-in intent, dry runs, JSON output, and catalog-driven integrations." +currentState: "The project centers on the recipe CLI, a shared integration catalog, and the Calavera Composer site, with modern, classic, and minimal tooling profiles." +nextSteps: + - "Preserve parity between the CLI and web composer as integrations evolve." + - "Improve generated tooling quality while keeping files readable and reviewable." + - "Keep automation-friendly commands, dry runs, and JSON output stable for agents and CI." +contributionGuidance: "Start with catalog-level changes and tests for generated dependencies, scripts, and managed files. New integrations should stay small, explicit, and easy to inspect in dry-run output." +--- diff --git a/src/content/projects/css-benchpress.md b/src/content/projects/css-benchpress.md new file mode 100644 index 00000000..673fe100 --- /dev/null +++ b/src/content/projects/css-benchpress.md @@ -0,0 +1,20 @@ +--- +title: "css-benchpress" +description: "Grow realistic web-platform CSS test cases until measurable performance regressions appear." +category: "main" +order: 8 +repoUrl: "https://github.com/schalkneethling/css-benchpress" +imageUrl: "https://repository-images.githubusercontent.com/1272350957/e3bc5088-f054-46b0-86ab-1c5fdea570b0" +language: "TypeScript" +stars: 9 +technologies: ["TypeScript", "CSS", "Performance"] +goalDocUrl: "https://github.com/schalkneethling/css-benchpress/blob/main/GOAL.md" +whatAndWhy: "A CSS performance research tool that grows realistic web-platform cases until measurable regressions appear. It exists to turn vague concerns about expensive CSS patterns into shareable, reproducible evidence." +goalSummary: "Help CSS authors, tooling builders, and browser engineers discover where selectors, custom properties, layout movement, visual effects, and mutation patterns become performance problems." +currentState: "The first milestone is a Node.js and TypeScript CLI prototype using Playwright to run framework-free fixtures, gather standard Performance API and Chromium trace signals, detect thresholds, and save reports and repros." +nextSteps: + - "Ship the initial case corpus for selectors, custom property fanout, @property, layout instability, and paint-heavy effects." + - "Tune thresholds against real examples and reviewed reports." + - "Make generated artifacts clear enough to share with DevTools authors and browser engineers." +contributionGuidance: "Useful contributions are focused, realistic CSS cases with clear scaling dimensions and expected signals. Avoid leaderboard-style comparisons; the goal is credible diagnosis and reproducibility." +--- diff --git a/src/content/projects/css-community-reset.md b/src/content/projects/css-community-reset.md new file mode 100644 index 00000000..2ce4bdc3 --- /dev/null +++ b/src/content/projects/css-community-reset.md @@ -0,0 +1,19 @@ +--- +title: "css-community-reset" +description: "A CSS reset inspired by the community." +category: "main" +order: 1 +repoUrl: "https://github.com/schalkneethling/css-community-reset" +imageUrl: "https://opengraph.githubassets.com/8ee4947881a615c98b4308d4e1887b93924348633c77074b9cd7908d0bb002ee/schalkneethling/css-community-reset" +language: "CSS" +stars: 16 +technologies: ["CSS"] +whatAndWhy: "A small, opinionated reset that collects the CSS defaults I keep wanting at the start of new projects. It exists as a practical notebook: part reusable baseline, part record of community thinking around modern resets." +goalSummary: "Make the opening move of a stylesheet feel less repetitive while still staying readable enough that someone can copy, audit, and adapt it." +currentState: "The project is a compact CSS package with a README that credits the reset work and browser-behavior notes it draws from." +nextSteps: + - "Keep the reset intentionally small as browser defaults and community practice shift." + - "Document the reasoning behind each rule so changes remain easy to review." + - "Add GOAL.md and ROADMAP.md when the project moves into the shared project-page workflow." +contributionGuidance: "The best contributions are focused suggestions with a clear browser behavior, accessibility, or typography reason behind them. Open an issue before broadening the reset." +--- diff --git a/src/content/projects/css-custom-property-inspector.md b/src/content/projects/css-custom-property-inspector.md new file mode 100644 index 00000000..ec548931 --- /dev/null +++ b/src/content/projects/css-custom-property-inspector.md @@ -0,0 +1,19 @@ +--- +title: "css-custom-property-inspector" +description: "A VS Code extension that shows CSS custom property values, definitions, and resolved references on hover." +category: "main" +order: 9 +repoUrl: "https://github.com/schalkneethling/css-custom-property-inspector" +imageUrl: "https://opengraph.githubassets.com/8ee606d555f413fb3414dff7e8d8280ae7ffc9305fc38d5e353e2d5ecf86a707/schalkneethling/css-custom-property-inspector" +language: "TypeScript" +stars: 0 +technologies: ["TypeScript", "VS Code", "CSS"] +whatAndWhy: "A VS Code-compatible extension for inspecting CSS custom properties from the editor. It exists because design-token systems are easier to maintain when authors can see values, definitions, fallbacks, and reference chains without manually searching a workspace." +goalSummary: "Give CSS, SCSS, Less, and HTML authors useful hover feedback for custom property definitions, resolved var() chains, color swatches, selector context, and clickable source locations." +currentState: "The extension scans workspace stylesheets and HTML style blocks, keeps an index fresh through file watching, and exposes configurable hover behavior for resolved values, depth, include types, excludes, and file links." +nextSteps: + - "Add a project GOAL.md so the long-term editor-tooling direction is captured outside the README." + - "Expand real-workspace test coverage around nested references, media-query context, and fallback handling." + - "Clarify compatibility and release expectations for VS Code, VS Codium, Cursor, and Windsurf users." +contributionGuidance: "Strong contributions include reproducible CSS workspaces, hover-output expectations, and fixes that keep indexing accurate without making editor feedback noisy." +--- diff --git a/src/content/projects/css-expect.md b/src/content/projects/css-expect.md new file mode 100644 index 00000000..f405287c --- /dev/null +++ b/src/content/projects/css-expect.md @@ -0,0 +1,20 @@ +--- +title: "css-expect" +description: "Write browser-native expectations for CSS custom functions and mixins." +category: "main" +order: 5 +repoUrl: "https://github.com/schalkneethling/css-expect" +imageUrl: "https://repository-images.githubusercontent.com/1258634645/be6e459a-0cd5-47db-b6bb-d4b1789638d6" +language: "TypeScript" +stars: 4 +technologies: ["TypeScript", "CSS", "Testing"] +goalDocUrl: "https://github.com/schalkneethling/css-expect/blob/main/GOAL.md" +whatAndWhy: "A small browser-backed expectation library for CSS custom functions, and eventually native CSS mixins. It exists because emerging CSS logic should be tested by the browser that computes it, not by a JavaScript reimplementation guessing at CSS semantics." +goalSummary: "Make it practical to write trustworthy tests for native CSS custom functions by loading CSS, applying a real property context, reading computed values, and returning diagnostics that explain browser support and expectation failures." +currentState: "The project is focused on proving the custom-function API against current Chromium support, with Playwright as the browser automation layer and a conservative package surface for early adopters." +nextSteps: + - "Keep README examples and generated diagnostics aligned with the shipped API." + - "Validate behavior when target CSS features are unsupported and make skip-or-fail policies clear." + - "Prepare the package for a careful first public release." +contributionGuidance: "Useful contributions are browser-backed test cases, clearer diagnostics, and examples that show real custom-function usage. Avoid adding broad runner integrations until the core API has settled." +--- diff --git a/src/content/projects/css-media-pseudo-polyfill.md b/src/content/projects/css-media-pseudo-polyfill.md new file mode 100644 index 00000000..428dac18 --- /dev/null +++ b/src/content/projects/css-media-pseudo-polyfill.md @@ -0,0 +1,19 @@ +--- +title: "css-media-pseudo-polyfill" +description: "A CSS polyfill for media pseudo-classes such as :playing, :paused, :seeking, and :muted." +category: "main" +order: 11 +repoUrl: "https://github.com/schalkneethling/css-media-pseudo-polyfill" +imageUrl: "https://opengraph.githubassets.com/3e4f3b4145c5fce35fd49cf5f9b83806101eee6f02b2a6b937511bcdf1de65fc/schalkneethling/css-media-pseudo-polyfill" +language: "TypeScript" +stars: 3 +technologies: ["TypeScript", "CSS", "Polyfill"] +whatAndWhy: "A progressive polyfill for media state pseudo-classes such as :playing, :paused, :seeking, :buffering, :stalled, and :muted. It exists so authors can write state-driven media styles now while browsers continue filling support gaps." +goalSummary: "Detect unsupported media pseudo-classes, preserve authored cascade intent by rewriting CSS into specificity-equivalent class selectors, and update audio and video elements as their playback state changes." +currentState: "The package supports per-pseudo-class feature detection, inline and same-origin linked stylesheet rewriting, MutationObserver-based media discovery, pure state computation, and explicit handling for non-polyfillable :volume-locked." +nextSteps: + - "Add GOAL.md and ROADMAP.md so the polyfill scope and future removal path are captured outside the README." + - "Keep tests focused on CSS rewriting, state computation, same-origin stylesheet behavior, and dynamic media elements." + - "Document browser support and FOUC tradeoffs as native media pseudo-class support changes." +contributionGuidance: "Helpful contributions include reduced browser cases, css-tree rewriting tests, and media-state edge cases. Keep the polyfill progressive and avoid expanding into behavior the DOM cannot observe." +--- diff --git a/src/content/projects/css-property-type-validator.md b/src/content/projects/css-property-type-validator.md new file mode 100644 index 00000000..bcb749ad --- /dev/null +++ b/src/content/projects/css-property-type-validator.md @@ -0,0 +1,22 @@ +--- +title: "css-property-type-validator" +description: "Standalone tooling for validating CSS custom property registrations declared with @property." +category: "main" +order: 6 +repoUrl: "https://github.com/schalkneethling/css-property-type-validator" +imageUrl: "https://repository-images.githubusercontent.com/1198765576/0966a7fe-ab8b-4e6d-997e-5dbb136ff80d" +liveUrl: "https://typedcss-validator.schalkneethling.com" +language: "TypeScript" +stars: 10 +technologies: ["TypeScript", "CSS", "Tooling"] +goalDocUrl: "https://github.com/schalkneethling/css-property-type-validator/blob/main/GOAL.md" +roadmapDocUrl: "https://github.com/schalkneethling/css-property-type-validator/blob/main/ROADMAP.md" +whatAndWhy: "A validator for typed CSS custom properties and @property registrations. It exists because invalid registrations and incompatible var() usage can silently change rendered output, especially in token-heavy systems." +goalSummary: "Make typed custom properties easier to adopt by catching invalid @property descriptors, incompatible assignments, and risky var() usage through a shared validation core that can power a CLI, Stylelint plugin, editor tooling, and a browser UI." +currentState: "The core validates registrations, direct assignments, multiple registered var() usages, simple fallback branches, imported registries, and a first beta Stylelint plugin." +nextSteps: + - "Improve support for whitespace and fallback-toggle custom property patterns." + - "Add clearer remediation context to diagnostics." + - "Harden Stylelint beta behavior through real-project feedback and config-file based registry discovery." +contributionGuidance: "The best contributions include reduced CSS examples, expected diagnostics, and tests against the shared core. Keep behavior conservative and prefer skipping uncertain cases over noisy false positives." +--- diff --git a/src/content/projects/css-tree-ast-viewer.md b/src/content/projects/css-tree-ast-viewer.md new file mode 100644 index 00000000..97832151 --- /dev/null +++ b/src/content/projects/css-tree-ast-viewer.md @@ -0,0 +1,11 @@ +--- +title: "css-tree-ast-viewer" +description: "A focused CSS AST explorer for writing CSS in one pane and viewing the CSS Tree AST in another." +category: "demo" +order: 2 +repoUrl: "https://github.com/schalkneethling/css-tree-ast-viewer" +imageUrl: "https://repository-images.githubusercontent.com/1190851472/d5be03b0-23c7-4406-a967-f7c4b085929d" +language: "TypeScript" +stars: 1 +technologies: ["TypeScript", "CSS", "AST"] +--- diff --git a/src/content/projects/jsconsole.md b/src/content/projects/jsconsole.md new file mode 100644 index 00000000..e7702d19 --- /dev/null +++ b/src/content/projects/jsconsole.md @@ -0,0 +1,11 @@ +--- +title: "jsconsole" +description: "A focused JavaScript REPL for quickly testing browser-style JavaScript snippets." +category: "demo" +order: 3 +repoUrl: "https://github.com/schalkneethling/jsconsole" +imageUrl: "https://opengraph.githubassets.com/e418ca0eafcc3e8d7a81ff50d551b0e4e73d3c27eed30ce504c0f58bddcb4719/schalkneethling/jsconsole" +language: "TypeScript" +stars: 0 +technologies: ["TypeScript", "JavaScript", "REPL"] +--- diff --git a/src/content/projects/little-demos.md b/src/content/projects/little-demos.md new file mode 100644 index 00000000..16507a3b --- /dev/null +++ b/src/content/projects/little-demos.md @@ -0,0 +1,11 @@ +--- +title: "little-demos" +description: "A collection of little web platform feature demos." +category: "demo" +order: 1 +repoUrl: "https://github.com/schalkneethling/little-demos" +imageUrl: "https://opengraph.githubassets.com/904c2e7a34440a8e86746a299d491f82b96be028d7c69d6f27e8dc11b03bdc73/schalkneethling/little-demos" +language: "CSS" +stars: 0 +technologies: ["HTML", "CSS", "JavaScript"] +--- diff --git a/src/content/projects/masonry-gridlanes-wc.md b/src/content/projects/masonry-gridlanes-wc.md new file mode 100644 index 00000000..00275450 --- /dev/null +++ b/src/content/projects/masonry-gridlanes-wc.md @@ -0,0 +1,20 @@ +--- +title: "masonry-gridlanes-wc" +description: "A light-DOM custom element for CSS Grid Lanes masonry with a JavaScript fallback." +category: "main" +order: 4 +repoUrl: "https://github.com/schalkneethling/masonry-gridlanes-wc" +imageUrl: "https://opengraph.githubassets.com/69ce968cb624e4cde1f74edf54d4dd27c018a4ec1fb692a82b0672d0ed04bee6/schalkneethling/masonry-gridlanes-wc" +language: "JavaScript" +stars: 11 +technologies: ["JavaScript", "Web Components", "CSS Grid"] +goalDocUrl: "https://github.com/schalkneethling/masonry-gridlanes-wc/blob/main/GOAL.md" +whatAndWhy: "A native-first Web Component for masonry layouts that tracks the emerging CSS Grid Lanes model. It exists to let real sites experiment with platform-shaped masonry today, while keeping markup and CSS close enough to future native support that removal stays realistic." +goalSummary: "Make CSS Grid Lanes masonry practical for production sites through a light-DOM custom element that prefers native support and offers a focused, spec-shaped JavaScript fallback where the platform is not ready yet." +currentState: "The package has a 0.1.x foundation with column masonry, row-mode experimentation, demos, Playwright coverage, and Pretext-powered helpers for text-heavy layouts." +nextSteps: + - "Strengthen confidence in column masonry and text-heavy layouts through focused tests and demos." + - "Improve row-mode behavior while keeping its constraints clear." + - "Keep the fallback honest about what it supports instead of presenting it as a full CSS Grid Lanes polyfill." +contributionGuidance: "Good contributions are small behavior fixes, demo improvements, or tests that clarify native-vs-fallback expectations. Open an issue first when changing fallback layout behavior." +--- diff --git a/src/content/projects/ossreleasefeed-v2.md b/src/content/projects/ossreleasefeed-v2.md new file mode 100644 index 00000000..a9d1e3d8 --- /dev/null +++ b/src/content/projects/ossreleasefeed-v2.md @@ -0,0 +1,19 @@ +--- +title: "ossreleasefeed-v2" +description: "Create custom RSS release feeds for GitHub topic repositories or starred projects." +category: "main" +order: 2 +repoUrl: "https://github.com/schalkneethling/ossreleasefeed-v2" +imageUrl: "https://opengraph.githubassets.com/582d0382d7093bcd8c120f0ae972246626b09ef5b27f6a3c008556cb4b22ad22/schalkneethling/ossreleasefeed-v2" +language: "TypeScript" +stars: 0 +technologies: ["TypeScript", "RSS", "GitHub"] +whatAndWhy: "OSSReleaseFeed creates permanent feed URLs for GitHub release activity, without asking people to create an account or connect OAuth. It is for the person who still wants the quiet reliability of a feed reader when tracking open source releases." +goalSummary: "Provide lightweight Atom and JSON feeds for repositories discovered through GitHub topics or a user's starred projects, keeping the public surface simple: configure preferences, keep the URL, read releases wherever you already read feeds." +currentState: "The rewrite is under active development and is not yet in public beta. The planned stack combines a React/Vite frontend with a Cloudflare Worker backend built with Hono, Effect, and TypeScript." +nextSteps: + - "Ship the first public beta once the feed-generation path is stable." + - "Harden topic and starred-repository feed behavior before inviting broad usage." + - "Add project docs that can feed the longer public project page without making builds depend on GitHub." +contributionGuidance: "The repository is not open for outside contributions yet. For now, the useful way to engage is to watch the repo, follow issues as they appear, and try the beta once it is announced." +--- diff --git a/src/content/projects/refined-plan-mode.md b/src/content/projects/refined-plan-mode.md new file mode 100644 index 00000000..40c3de94 --- /dev/null +++ b/src/content/projects/refined-plan-mode.md @@ -0,0 +1,20 @@ +--- +title: "refined-plan-mode" +description: "A local web app for reviewing AI coding-agent plans with anchored feedback." +category: "main" +order: 13 +repoUrl: "https://github.com/schalkneethling/refined-plan-mode" +imageUrl: "https://opengraph.githubassets.com/29924b3161f878801383457c29b92083fb0ae345ba4523d6ffb9f984940cbb3c/schalkneethling/refined-plan-mode" +language: "TypeScript" +stars: 3 +technologies: ["TypeScript", "AI Agents", "Review Tools"] +goalDocUrl: "https://github.com/schalkneethling/refined-plan-mode/blob/main/GOAL.md" +whatAndWhy: "A local review app and protocol for AI coding-agent plans. It exists because long plans need the same kind of anchored, reviewable feedback loop that pull requests give code." +goalSummary: "Turn agent plans into versioned local artifacts with browser-based line, range, and text-selection comments, structured feedback files, and an explicit approval gate before implementation." +currentState: "The MVP can read versioned Markdown plans, render them in a browser reviewer, persist draft comments, submit JSON feedback, and mark a plan approved through the .plan-review file convention." +nextSteps: + - "Solidify the local review loop around versioned plans, anchored comments, JSON feedback, and approval." + - "Make the reviewer UI faster and more natural for repeated plan review." + - "Keep Codex, Claude Code, and future harness integrations behaviorally consistent around the same file protocol." +contributionGuidance: "Useful contributions improve the local file protocol, reviewer ergonomics, harness instructions, and tests around plan review state transitions. Keep the workflow local-first and easy to recover by reading files." +--- diff --git a/src/content/projects/skills-autoresearch-flue.md b/src/content/projects/skills-autoresearch-flue.md new file mode 100644 index 00000000..290e7df9 --- /dev/null +++ b/src/content/projects/skills-autoresearch-flue.md @@ -0,0 +1,20 @@ +--- +title: "skills-autoresearch-flue" +description: "A Flue agent harness for autoresearching, evaluating, and improving agent skills." +category: "main" +order: 12 +repoUrl: "https://github.com/schalkneethling/skills-autoresearch-flue" +imageUrl: "https://repository-images.githubusercontent.com/1229035215/462a7f1d-5fdf-4c7c-a6f6-ee53437a2a43" +language: "TypeScript" +stars: 12 +technologies: ["TypeScript", "Agents", "Evaluation"] +goalDocUrl: "https://github.com/schalkneethling/skills-autoresearch-flue/blob/main/GOAL.md" +whatAndWhy: "A Flue-based research harness for deciding whether agent skills help, how much context they should contain, and when a model no longer needs them. It exists to make skill changes evidence-driven instead of vibes-driven." +goalSummary: "Run auditable eval loops across producer models, compare no-skill and skill-guided output, inspect scores and costs, and let a researcher agent propose the smallest useful skill change when guidance is justified." +currentState: "The alpha centers on a release-notes fixture with baseline import, optional research, producer evals, independent judging, score aggregation, cost summaries, and persisted artifacts." +nextSteps: + - "Tighten resume and retry behavior without overwriting audit evidence." + - "Keep documentation, examples, schemas, and fixture behavior aligned as the alpha loop changes." + - "Explore cross-provider judging after the single-provider flow is stable enough for meaningful comparisons." +contributionGuidance: "Contributions should preserve researcher, producer, and judge separation. Favor small fixtures, schema-validated artifacts, and changes that make evidence easier to inspect by hand." +--- diff --git a/src/content/projects/web-platform-pulse.md b/src/content/projects/web-platform-pulse.md new file mode 100644 index 00000000..88d328fe --- /dev/null +++ b/src/content/projects/web-platform-pulse.md @@ -0,0 +1,19 @@ +--- +title: "web-platform-pulse" +description: "Keep your finger on the pulse of the web platform." +category: "main" +order: 3 +repoUrl: "https://github.com/schalkneethling/web-platform-pulse" +imageUrl: "https://opengraph.githubassets.com/e3937f860da25df07b660fed4a1f0e0a4294d43ee02958b08b7bb05d693b856f/schalkneethling/web-platform-pulse" +language: "TypeScript" +stars: 0 +technologies: ["TypeScript", "Web Platform"] +whatAndWhy: "Web Platform Pulse is a daily digest for the web platform: Baseline transitions, browser support changes, browser releases, runtime releases, and standards-position movement in one place. It exists because the raw signals are valuable, but scattered." +goalSummary: "Turn many noisy source feeds into a small, trustworthy digest with provenance links, deduplication, and a reader/email delivery model that makes platform change easier to follow." +currentState: "The prototype has a documented pipeline shape, source adapters, significance scoring, persistence, email delivery, and a small reader app. Deployment notes currently target free-tier services." +nextSteps: + - "Keep improving source adapters and correlation so repeated runs stay idempotent." + - "Tune significance scoring as real-world changes reveal what readers actually need first." + - "Move from prototype deployment notes toward a more durable public operating model." +contributionGuidance: "Helpful contributions are likely to be source-specific fixes, digest-quality improvements, or test cases that capture subtle web platform change events. Start with an issue so the signal stays connected to the project goal." +--- diff --git a/src/data/project-doc-cache/README.md b/src/data/project-doc-cache/README.md new file mode 100644 index 00000000..2aa14429 --- /dev/null +++ b/src/data/project-doc-cache/README.md @@ -0,0 +1,14 @@ +# Project doc cache + +This directory contains manually refreshed snapshots of public project +`GOAL.md` and `ROADMAP.md` documents. + +Refresh the cache with: + +```sh +pnpm run refresh:project-docs +``` + +The refresh is intentionally not part of the build. Public project pages should +continue to use editorial summaries, with this cache available as source +material for future updates. diff --git a/src/data/project-doc-cache/common-components.json b/src/data/project-doc-cache/common-components.json new file mode 100644 index 00000000..9286a8e4 --- /dev/null +++ b/src/data/project-doc-cache/common-components.json @@ -0,0 +1,22 @@ +{ + "id": "common-components", + "title": "common-components", + "repoUrl": "https://github.com/schalkneethling/common-components", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/common-components/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\nMake Fiori a practical library of real, usable, accessible web platform components that people can copy, study, adapt, and use in their projects.\n\nThe library should push the boundaries of the web platform, including Baseline Newly available and Limited availability features when they make a component better. That ambition must be paired with clear component-level documentation: production readiness, browser support, feature availability, required polyfills, and dependencies should be visible before a developer invests time adopting a component.\n\nThe supporting purpose is to make Fiori a realistic test bed and proving ground for the tooling ecosystem around it. The latest Miyagi should be integrated with the project, and projects such as Project Calavera, the CSS property type validator, axe aggregate reporter, claude-toolkit, autoresearch, and VS Code extensions should be able to demonstrate their value here against a real-world component library rather than contrived fixtures. The repo should also explore how standards-based Web Components can work inside a React context, where `fiori-react` becomes the source of truth for using these components from React without replacing their platform foundation.\n\n## Who This Is For\n\nThis project is primarily for developers and maintainers who need common web components with clear HTML, CSS, JavaScript, accessibility behavior, documentation, tests, support status, and integration notes.\n\nIt is also for the maintainer and contributors who want a practical project laboratory, React developers who want to use Web Components without losing React ergonomics, and users of the wider toolchain who need believable proof points: component folders for Miyagi, repeatable tooling recipes for Project Calavera, typed CSS and custom-property examples for CSS tools, Playwright pages for accessibility reporting, and real project context for agent skills and VS Code extensions.\n\n## Core Goals\n\n1. Ship useful, accessible, platform-first components.\n Components should solve recognizable interface needs such as alerts, skip links, disclosure, icons, session warnings, cards, menus, forms, galleries, and sliders. They should prefer native HTML, CSS, and Web Components before framework code. Lit is encouraged when it improves developer experience, code clarity, and maintainability.\n\n2. Establish a reference component quality bar.\n At least one component should demonstrate the complete standard: README, demo page, API notes, accessible markup, unit tests where useful, Playwright coverage, visual or accessibility checks, support status, feature availability, dependency and polyfill notes, and clear maintenance boundaries.\n\n3. Separate production-ready components from experiments.\n The repo can use modern and emerging platform features, but every component must clearly state whether it is production-ready, production-ready with documented polyfills or dependencies, experimental, or archived. This prevents the catalog from implying maturity or browser support that the code does not yet have.\n\n4. Use the repo as an integration playground for sibling projects.\n Fiori should intentionally exercise Miyagi component docs and mock data, Project Calavera tooling recipes, CSS property validation, Playwright accessibility aggregation, agent-skill workflows, and VS Code custom-property tools.\n\n5. Make React usage first-class without making React the implementation source.\n The `fiori-react` app should become the source of truth for using Fiori components in React. It should show how the Web Components behave in React applications, where thin React wrappers help, how custom events map into React handlers, how refs and imperative APIs are exposed, and what documentation React users need.\n\n6. Keep the project easy to inspect and run.\n A contributor or agent should be able to install dependencies, start the catalog, run tests, understand component status, and pick the next useful task without reverse-engineering the repository.\n\n7. Build a small, credible contribution surface.\n Issues, labels, documentation, and tests should guide improvements around accessibility, browser behavior, component APIs, CSS quality, and examples.\n\n## Success Looks Like\n\n- The root README clearly explains what Fiori is, how to run it, which components exist, and how production-ready or experimental each one is.\n- A user can open the catalog, inspect a component, understand its accessibility model, and reuse the code with confidence.\n- One component, likely `alertbox`, becomes the reference implementation for documentation, tests, accessibility behavior, events, configuration, and release readiness.\n- Every component has a support status, owner intent, and next action.\n- Every component that uses Baseline Newly available or Limited availability features documents browser support, fallbacks, polyfills, dependencies, and production suitability.\n- The package-manager transition is complete: lockfile, scripts, CI, docs, and local commands agree.\n- CI runs formatting or linting, unit tests, Playwright tests, and accessibility checks with useful artifacts.\n- The latest Miyagi is integrated with the project and can present the component catalog, documentation, and examples.\n- The `fiori-react` app documents canonical React usage for the components, including props/attributes, events, refs, and accessibility expectations.\n- Project Calavera can apply or inspect the repo's tooling recipe as a realistic web-component project.\n- CSS custom properties and `@property` registrations are structured enough to exercise the CSS property type validator and VS Code custom-property tools.\n- Playwright accessibility runs can feed axe aggregate reporter with realistic component-page results.\n- Agent-facing docs and skills can use this repo as a concrete fixture for semantic HTML, component review, CSS validation, and accessibility remediation.\n\n## Non-Goals\n\n- This is not a broad UI framework or a full design system.\n- This is not trying to replace mature component libraries.\n- This should not optimize for framework bindings before the platform components and demos are solid.\n- React wrappers should not become the canonical implementation of a component; they are the canonical React usage layer and adoption helpers.\n- This should not publish every experiment as production-ready.\n- This should not hide complex behavior behind heavy build tooling when a plain web-platform example would be clearer.\n- This should not become only a test fixture or tooling demo. The components should remain understandable, accessible, and useful to humans.\n- This should not promise production support for emerging APIs unless support limits, fallbacks, polyfills, and dependencies are documented clearly.\n\n## Principles and Constraints\n\n- Accessibility is part of the component contract, not a later audit phase.\n- Prefer semantic HTML, native behavior, logical CSS properties, and small JavaScript modules.\n- Push the web platform deliberately. Baseline Newly available and Limited availability features are welcome when they teach something or make the component better, as long as support status is explicit.\n- Dependencies, including Lit and polyfills, are allowed when they clearly improve developer experience, correctness, or maintainability, but the README and component docs must not claim \"no external dependencies\" when runtime dependencies exist.\n- Documentation, demos, tests, and implementation should describe the same behavior.\n- React integration should reveal friction transparently rather than hiding Web Component semantics behind heavy abstractions.\n- Experiments are welcome when clearly labelled.\n- Generated assets and large media should support real examples, not obscure the component behavior.\n- The repo should stay friendly to automated agents: clear docs, stable commands, small fixtures, and explicit current focus.\n- Existing user work in progress, including the pnpm migration, should be preserved and completed intentionally.\n\n## Current Focus\n\nThe first turnaround milestone is to stabilize the project shape and create actionable GitHub issues from this goal:\n\n1. Complete the pnpm migration across local scripts, CI, and docs.\n2. Rewrite the root README around Fiori's purpose, readiness model, and integration surface.\n3. Create a component inventory with support status and next actions.\n4. File and prioritize issues for reference components, React usage, Miyagi integration, accessibility reporting, CSS validation, Project Calavera, and documentation.\n\n## Open Questions\n\n- None at the project-goal level. Component sequencing and first React examples should be decided during issue filing and prioritization.\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/common-components/main/ROADMAP.md", + "status": "fetched", + "statusCode": 200, + "content": "# Fiori Turnaround Roadmap\n\n## Stocktake\n\n### What Is Already Here\n\n- A Vite-powered static catalog at `index.html`.\n- A root package named `fiori-common-components`.\n- A mostly migrated pnpm setup, with `pnpm-lock.yaml` present and `package-lock.json` deleted in the working tree.\n- Component demos under `components/`, including `alertbox`, `svg-icon`, `fiori-disclosure`, `session-end-alert`, `skip-links-nav`, `card-with-flyout`, `carousel`, `curated-gallery`, `forms`, `megamenu`, `hero`, and several Fiori experiments.\n- A separate `fiori-react` Vite React app that is still essentially starter scaffolding, but can become a focused React interoperability lab.\n- Unit-style Vitest coverage for `alertbox`.\n- Playwright coverage for `alertbox` pages.\n- Repository settings with issue labels and one open issue for `session-end-alert` storage behavior.\n\n### Strongest Existing Asset\n\n`alertbox` is the closest thing to a production candidate. It already has:\n\n- Custom elements for manager and banner rendering.\n- JSON configuration.\n- Zod validation.\n- Dismissal behavior across page, session, and permanent storage.\n- Date-range support.\n- Action buttons and links.\n- Event emission.\n- Unit and Playwright tests.\n- Multi-page examples.\n\nThis should become the reference component before broadening the project, but its validation approach should be reviewed rather than treated as a permanent default. Zod may remain the right choice, but lighter options such as Valibot or plain purpose-built validation may be better for some components.\n\n### Main Problems\n\n- The root README is too thin and partly stale. It says the project has no external dependencies, but `zod` is currently a runtime dependency.\n- CI still uses `npm ci`, while the branch appears to be moving to pnpm.\n- Component maturity is unclear. Some folders look like reusable components, some are experiments, and some are demos with no docs.\n- Modern platform feature support is not surfaced consistently. Developers need to know up front whether a component is production-ready, experimental, requires polyfills, or depends on Baseline Newly available or Limited availability features.\n- Runtime validation choices are not yet governed by component-level criteria. Some components may need a schema library, while others may be better served by lighter validation or no library at all.\n- The root catalog links only a subset of components.\n- Accessibility is an obvious theme, but there is no automated axe reporting yet.\n- There is no documented relationship to the latest Miyagi, CSS validation, axe aggregate reporter, claude-toolkit, autoresearch, or the VS Code extensions.\n- `fiori-react` is present but does not yet demonstrate how these Web Components behave in React.\n\n## Strategy\n\nBuild the repo first as a practical component library, then use that real project as a tooling proving ground.\n\nThe component library gives the project intrinsic value. The proving ground gives the wider ecosystem a realistic test target and public demonstration surface. Those two purposes reinforce each other as long as component maturity is explicit and the tooling integrations improve the component quality bar instead of distracting from it.\n\nFiori should also push the web platform. Components may use Baseline Newly available and Limited availability features when they are the right tool, but the catalog, README, and component docs must make support status, polyfills, dependencies, and production suitability impossible to miss.\n\nReact interoperability is part of that proof surface, but it has a special role: `fiori-react` should become the canonical source for using the components from React without moving the canonical component implementation into React.\n\n## Milestones\n\n### Milestone 1: Make The Project Coherent\n\nGoal: a contributor or agent can understand the repo in five minutes.\n\n- Complete pnpm migration in scripts, CI, README, and local setup.\n- Update the README with purpose, install/run/test commands, component inventory, readiness labels, feature-support labels, dependency/polyfill notes, and contribution notes.\n- Add or update `CONTRIBUTING.md`.\n- Add a component status table that distinguishes production-ready, production-ready with documented polyfills or dependencies, experimental, and archived.\n- Reframe `fiori-react` as an active React interoperability lab.\n- Ensure the root catalog links every active demo or clearly links only supported demos.\n\nDone when:\n\n- Fresh checkout instructions work.\n- CI uses the same package manager as local development.\n- Every component has a documented support status and next action.\n\n### Milestone 2: Make The Catalog Useful To Real Users\n\nGoal: the first screen and component pages feel like a small, usable component library rather than a folder index.\n\n- Redesign the root catalog around component discovery, status, and usage.\n- Give each active component a consistent page shape: demo, usage, accessibility notes, API/configuration, browser support, Baseline/feature availability, dependencies, polyfills, and known limitations.\n- Keep copy-and-adapt usage paths obvious for dependency-light components.\n- Clearly separate supported components from experiments.\n- Make project naming consistently Fiori, with repository/package naming explained where needed.\n\nDone when:\n\n- A user can find a component, understand whether it is ready to use in production today, and copy the minimum required files or markup.\n- Experiments remain visible only where their status is clear.\n\n### Milestone 3: Promote `alertbox` To Reference Quality\n\nGoal: one component sets the bar for all future work.\n\n- Review `alertbox` API and storage semantics.\n- Review `alertbox` validation needs and compare Zod with lighter options such as Valibot or purpose-built validation.\n- Tighten date-range logic to avoid locale-string comparison risks.\n- Confirm accessible names, roles, focus behavior, and announcement behavior.\n- Add axe checks for all `alertbox` demo pages.\n- Add documentation for events, validation failures, storage behavior, browser support, dependencies, and production readiness.\n- Consider whether `svg-icon` is an internal dependency, a peer component, or a required bundled asset.\n\nDone when:\n\n- `alertbox` has docs, demos, unit tests, Playwright tests, accessibility checks, and clear known limitations.\n- Future components can copy its documentation and test structure.\n\n### Milestone 4: Wire In The Tooling Ecosystem\n\nGoal: sibling projects have a real, maintained fixture.\n\n- Integrate the latest Miyagi so it can read component folders, docs, examples, and mock data.\n- Add a Project Calavera recipe or audit path for the repo's linting, formatting, TypeScript, Stylelint, accessibility, and CSS validation setup.\n- Add CSS custom-property examples and optional `@property` registrations for the CSS property type validator.\n- Add an accessibility test script that produces axe aggregate reporter input.\n- Add a generated or documented report workflow for local review.\n- Add claude-toolkit or Codex skill guidance for component review, semantic HTML, accessibility remediation, and CSS validation.\n- Add an autoresearch fixture that uses one component task as a repeatable eval.\n\nDone when:\n\n- At least three sibling tools can run against this repo with documented commands and meaningful output.\n\n### Milestone 5: Build The React Interoperability Lab\n\nGoal: make `fiori-react` the source of truth for using Fiori components in React, without making React the component implementation source.\n\n- Replace the Vite starter content in `fiori-react` with a real integration example.\n- Keep the lab on React 19 and evaluate React Compiler where it improves real examples without adding confusing build complexity.\n- Create React usage examples for all reference-ready components, with issue prioritization deciding the order.\n- Demonstrate attribute/property mapping, custom events, refs or imperative methods, and styling hooks.\n- Decide when a thin React wrapper is useful and when direct custom-element usage is clearer.\n- Document TypeScript ergonomics for custom elements in JSX.\n- Add React-facing tests for the integration behavior that React users are likely to depend on.\n- Verify any React Compiler adoption through build output, linting, and observable behavior instead of enabling it by default without evidence.\n- Feed findings back into the Web Component API design so the platform components stay easier to consume from any framework.\n\nDone when:\n\n- `fiori-react` shows Fiori Web Components working in React with documented integration tradeoffs.\n- React 19 is the baseline for the React lab, and React Compiler usage is documented when enabled or explicitly deferred when not appropriate.\n- The React example improves the Web Component API or docs rather than forking behavior.\n\n### Milestone 6: Graduate The Next Components\n\nGoal: expand value without flattening all experiments into \"supported\" components.\n\nRecommended graduation order:\n\n1. `skip-links-nav`, because it is small, accessibility-centered, and already documented.\n2. `session-end-alert`, because it has a real open issue and clear product value.\n3. `svg-icon`, because other components already depend on it.\n4. `fiori-disclosure`, because it is a useful interactive primitive.\n\nFor each candidate:\n\n- Confirm intended use.\n- Define the public API.\n- Add or update README.\n- Add tests appropriate to behavior.\n- Add accessibility checks.\n- Add demo coverage in the root catalog and Miyagi.\n\nDone when:\n\n- At least three components meet the reference-quality checklist or are explicitly labelled as experiments.\n\n## Near-Term Issue Candidates\n\n- Finish pnpm migration and update GitHub Actions.\n- Rewrite the root README around the new goal and component inventory.\n- Create a component maturity table.\n- Make `alertbox` date-range comparisons timezone-safe and test them.\n- Review validation-library tradeoffs for `alertbox` and document when Fiori components should use Zod, Valibot, purpose-built validation, or no runtime validation library.\n- Add axe accessibility checks for `alertbox`.\n- Decide and document `session-end-alert` storage behavior, including whether storage should be configurable.\n- Integrate the latest Miyagi with the component folder structure.\n- Add Project Calavera configuration or docs for managing the repo's tooling recipe.\n- Add CSS custom-property validation examples.\n- Replace the `fiori-react` starter with Web Component integration examples.\n- Evaluate React Compiler for `fiori-react` once the first real examples exist.\n- File and prioritize GitHub issues from this roadmap, including component graduation order and React example order.\n\n## Component Maturity Draft\n\n| Component | Current Read | Suggested Status | Next Action |\n| --- | --- | --- | --- |\n| `alertbox` | Feature-rich with tests and docs | Reference candidate | Hardening, accessibility reporting, API review |\n| `skip-links-nav` | Small, documented, accessibility-focused | Active candidate | Add tests and catalog coverage |\n| `svg-icon` | Useful dependency for other components | Active candidate | Add tests and clarify packaging |\n| `session-end-alert` | Useful component with open storage question | Active candidate | Resolve configurable storage model |\n| `fiori-disclosure` | Documented Web Component primitive | Active candidate | Add tests and accessibility review |\n| `card-with-flyout` | Documented native popover/anchor experiment | Experiment or active candidate | Decide support browser/polyfill model |\n| `carousel` | Rich demo with many assets | Experiment | Clarify purpose and accessibility requirements |\n| `curated-gallery` | Asset-heavy demo | Experiment | Decide whether it is a component or content demo |\n| `megamenu` | Data-driven demo | Experiment | Add docs before promoting |\n| `forms` | Form option demos | Experiment | Inventory and document |\n| `hero` | Visual layout demo | Experiment | Decide whether it belongs in component catalog |\n| `fiori-wc` | Fiori experiments | Experiment | Consolidate or archive |\n| `disclosure-component-custom` | Older disclosure variant | Archive candidate | Compare with `fiori-disclosure` |\n| `responsive-slider` | Demo component | Experiment | Accessibility and API review |\n| `fiori-react` | Starter React app | Active integration lab | Replace starter with Web Component interoperability examples |\n\n## Decision Log\n\n- Treat `alertbox` as the first reference component unless future review exposes a better candidate.\n- Treat pnpm migration as current work in progress and complete it rather than reverting to npm.\n- Keep experiments for now, but label them clearly before inviting users or agents to rely on them.\n- Treat the usable component library as the primary product and the tooling ecosystem integration as its supporting proof surface.\n- Treat React integration as the canonical React usage layer and feedback loop for the Web Components, not as a replacement implementation track.\n- Encourage Lit when it improves developer experience, code clarity, and maintainability.\n- Choose runtime validation deliberately per component. Zod, Valibot, custom validation, and no validation are all valid outcomes depending on API shape, payload complexity, bundle impact, and maintainability.\n- Use React 19 as the React lab baseline, and adopt React Compiler only where it improves or validates realistic Fiori React usage.\n- Allow Baseline Newly available and Limited availability platform features when component docs clearly state readiness, browser support, polyfills, dependencies, and production suitability.\n" + } + } +} diff --git a/src/data/project-doc-cache/create-project-calavera.json b/src/data/project-doc-cache/create-project-calavera.json new file mode 100644 index 00000000..293ebab6 --- /dev/null +++ b/src/data/project-doc-cache/create-project-calavera.json @@ -0,0 +1,22 @@ +{ + "id": "create-project-calavera", + "title": "create-project-calavera", + "repoUrl": "https://github.com/schalkneethling/create-project-calavera", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/create-project-calavera/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\nCalavera helps web developers compose, apply, inspect, and refresh repeatable project tooling recipes without coupling those recipes to any one application framework or starter.\n\n## Who This Is For\n\n- Developers starting or maintaining web projects who want current linting, formatting, type-checking, and CSS-quality defaults without researching every tool from scratch.\n- Maintainers who want a checked-in tooling recipe that can be re-applied as project tooling evolves.\n- Agents and automation workflows that need deterministic CLI commands, dry runs, and JSON output when preparing or auditing project tooling.\n- Users who prefer a visual composer for choosing profiles and integration packs before applying the generated recipe with the CLI.\n\n## Core Goals\n\n1. Provide clear tooling profiles.\n - `modern` should favor newer, fast tools such as Oxlint, Oxfmt, Stylelint, and TypeScript.\n - `classic` should favor widely adopted ESLint, Prettier, Stylelint, and TypeScript workflows.\n - `minimal` should stay intentionally small, currently focused on EditorConfig.\n\n2. Make tooling configuration repeatable.\n - Store intent in `calavera.config.json`.\n - Generate managed config files, helper scripts, package scripts, and development dependencies from that recipe.\n - Track generated files in `.calavera/state.json` so stale managed files can be cleaned deliberately.\n\n3. Keep integrations catalog-first.\n - Add or change integrations primarily through `src/catalog.js` metadata: dependencies, parent integrations, plugin names, status, and tool-specific config.\n - Let the CLI and web composer consume that catalog rather than duplicating behavior by hand.\n - Make experimental and framework-specific integrations visible as such.\n\n4. Support safe inspection and automation.\n - Keep `doctor`, `apply --dry-run`, and `--json` output useful for humans, CI, and agents.\n - Prefer predictable file changes over hidden global state.\n - Make package-manager behavior explicit for npm, pnpm, yarn, and bun.\n\n5. Offer a small web composer.\n - Let users choose profiles, package managers, and integration packs in the browser.\n - Export a valid `calavera.config.json` that the CLI can apply.\n - Expose WebMCP tools so capable agents can read options, configure the form, and download the recipe.\n\n## Success Looks Like\n\n- A user can run `npm create project-calavera init`, review or edit `calavera.config.json`, run `apply`, and get working lint, format, type-check, and CSS tooling scripts.\n- `doctor`, `clean`, `update`, dry-run mode, and JSON mode give enough information to understand and automate changes before files are modified.\n- Adding a new integration requires a small, understandable catalog update plus focused generation logic only when the target tool truly needs it.\n- Generated files are readable, conventional, and easy to review, delete, or regenerate.\n- The web composer and CLI expose the same conceptual choices, so users do not have to learn two different product models.\n- The package can be validated, packed, and published through the existing secure npm trusted publishing workflow.\n\n## Non-Goals\n\n- Calavera is not an application scaffold. It should not generate app routes, components, business logic, UI systems, databases, or deployment-specific application code.\n- Calavera is not a framework replacement or framework opinion engine. It may offer framework-specific tooling packs, but it should not decide whether a project uses Vite, Astro, Next.js, Bun, React, Vue, Svelte, or another starter.\n- Calavera is not a machine setup tool. Editor extensions, global apps, shell profiles, operating-system packages, and developer workstation preferences are out of scope.\n- Calavera should not become a hidden build system. It should generate ordinary project files and package scripts that remain understandable without Calavera running constantly in the background.\n- Calavera should not optimize for every possible lint or formatting rule. Curated defaults, explicit optional packs, and maintainable catalog metadata matter more than exhaustive coverage.\n- Calavera should not silently overwrite unrelated project intent. Managed files and scripts should be predictable, inspectable, and recoverable through dry runs and state.\n\n## Principles and Constraints\n\n- Prefer checked-in project configuration over global or implicit state.\n- Preserve user agency: show choices, support dry runs, and make generated output easy to inspect.\n- Keep defaults practical for real projects, with modern speed where stable enough and classic compatibility where users need it.\n- Treat package-manager support as a first-class compatibility surface.\n- Keep the CLI usable in both interactive and non-interactive contexts.\n- Avoid coupling the catalog to the web UI in ways that make the CLI and composer drift.\n- Be clear when an integration is recommended, optional, framework-specific, or experimental.\n- Favor small, composable integration packs over large opaque presets.\n\n## Current Focus\n\nThe current project centers on the recipe-driven CLI, the shared integration catalog, and the Vite-based Calavera Composer. Near-term work should preserve parity between the CLI and composer, improve generated tooling quality, and keep automation-friendly outputs stable.\n\nThe public draft 2020-12 recipe schema is maintained in `web/public/calavera.config.schema.json` and published with the composer at `https://calavera.schalkneethling.com/calavera.config.schema.json`.\n\n## Open Questions\n\n- None currently tracked.\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/create-project-calavera/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/css-benchpress.json b/src/data/project-doc-cache/css-benchpress.json new file mode 100644 index 00000000..6bd87e4c --- /dev/null +++ b/src/data/project-doc-cache/css-benchpress.json @@ -0,0 +1,22 @@ +{ + "id": "css-benchpress", + "title": "css-benchpress", + "repoUrl": "https://github.com/schalkneethling/css-benchpress", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-benchpress/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\n`css-benchpress` exists to help developers and tooling authors discover where CSS patterns become performance problems by growing realistic web-platform test cases until measurable regressions appear.\n\n## Who This Is For\n\n`css-benchpress` is primarily for people investigating CSS performance:\n\n- DevTools and performance-tooling authors exploring what CSS profiling and explanation tools should show.\n- CSS authors and framework maintainers who want evidence about which platform patterns scale poorly and why.\n- Web platform and browser engineers who need reduced, repeatable cases to understand CSS performance behavior.\n\n## Core Goals\n\n1. Discover CSS performance regressions.\n\n Grow controlled fixtures across DOM size, selector shape, custom property fanout, visual effects, layout movement, and mutation frequency until a configured performance threshold is crossed.\n\n2. Produce reproducible artifacts.\n\n Save the smallest threshold-crossing case, samples, summaries, traces, and human-readable reports so others can inspect or reproduce the result.\n\n3. Keep cases web-platform focused.\n\n Use plain HTML, CSS, and JavaScript fixtures. Framework-inspired patterns may be represented as platform CSS, but bundled cases should not depend on framework runtimes.\n\n4. Combine portable and engine-specific signals.\n\n Use standard Performance APIs where possible, and Chromium trace data where deeper rendering-pipeline detail is needed.\n\n5. Inform future CSS tooling and authoring guidance.\n\n Use discovered regressions to clarify what a CSS profiler or CSS query explainer/planner should surface, and to help authors understand which CSS patterns deserve caution, scoping, containment, or alternative approaches.\n\n## Success Looks Like\n\nThe prototype is successful when it can:\n\n- Run a fixture through increasing scale steps.\n- Detect and report a clear regression point.\n- Save a reduced repro for the smallest bad scale.\n- Distinguish standard Performance API metrics from Chromium-only trace metrics.\n- Produce reports that explain what scaled, what regressed, and where to inspect next.\n- Provide enough real examples to start a concrete tooling and authoring-practice discussion with the wider web community.\n\nLonger-term success looks like a shared corpus of CSS performance cases that DevTools teams, framework authors, and CSS practitioners can contribute to and learn from.\n\n## Non-Goals\n\n`css-benchpress` is not:\n\n- A generic website performance audit tool.\n- A benchmark leaderboard for comparing sites, frameworks, or browsers.\n- A replacement for DevTools, Lighthouse, WebPageTest, or browser profilers.\n- A framework benchmark suite.\n- A polished CSS profiler UI in its first milestone.\n- A lab-grade benchmarking system before the basic regression-discovery model has proven useful.\n\n## Principles and Constraints\n\n- Prefer realistic, understandable cases over artificial worst cases.\n- Keep bundled fixtures framework-free to avoid introducing extra variables.\n- Treat standard web APIs as first-class signals, even when Chromium traces are needed for deeper detail.\n- Label metrics clearly as standard, experimental standard, or browser-specific.\n- Make generated artifacts easy to share with DevTools authors, browser engineers, and CSS practitioners.\n- Optimize first for credible discovery and reproducibility, then for statistical sophistication.\n- Design fixture and runner boundaries so Firefox and WebKit can be added later, even if Chromium has the richest metrics first.\n- Document research sources in the README so the project’s motivation and methodology are traceable.\n\n## Current Focus\n\nThe first milestone is a Node.js and TypeScript CLI prototype with Playwright automation that can:\n\n- list available cases;\n- run one case through a growth loop;\n- collect Performance API and Chromium CDP metrics;\n- detect threshold crossings;\n- write JSON and HTML reports;\n- save a smallest-scale repro;\n- ship an initial framework-free case corpus covering `:has()`, custom property fanout, sibling combinator selectors, `@property`, layout instability, and paint-heavy effects.\n\nSpeculative features such as `if()` and `@function` should remain experimental and skipped by default until browser support makes meaningful testing possible.\n\n## Open Questions\n\n- Which thresholds should each case use once initial reports can be compared with real user-impact research and field-performance expectations?\n- Which Chromium trace details matter most after generated reports have been reviewed by DevTools authors, browser engineers, and CSS practitioners?\n- What issue and pull request templates will keep the barrier to contributing a first case low while still encouraging clean code, instructive examples, and clear documentation?\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-benchpress/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/css-community-reset.json b/src/data/project-doc-cache/css-community-reset.json new file mode 100644 index 00000000..85587bd7 --- /dev/null +++ b/src/data/project-doc-cache/css-community-reset.json @@ -0,0 +1,22 @@ +{ + "id": "css-community-reset", + "title": "css-community-reset", + "repoUrl": "https://github.com/schalkneethling/css-community-reset", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-community-reset/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\nProvide a small, modern CSS reset that gives authors a practical starting point for accessible, predictable browser defaults while crediting and learning from community reset patterns.\n\n## Who This Is For\n\nThis project is for CSS authors and maintainers who want a focused reset stylesheet they can read, copy, and adapt into their own sites and projects.\n\n## Core Goals\n\n- Keep `reset.css` focused on browser defaults that commonly get in the way of reliable page styling.\n- Favor accessibility and user preferences, including readable text defaults, inherited form typography, responsive media behavior, stable scrolling behavior, and respect for platform font sizing.\n- Use modern CSS when it improves the baseline experience, while making support expectations explicit through comments and linting.\n- Document meaningful community influences and source material so maintainers can understand why a rule exists.\n- Maintain a simple project shape: one primary reset file, lightweight documentation, and formatting/linting tools that make changes reviewable.\n- Treat the repository as a copyable CSS resource rather than a JavaScript package with an application entry point.\n\n## Success Looks Like\n\n- The reset remains understandable enough to audit before use.\n- Each rule has a clear reason to exist and avoids surprising project-specific styling.\n- CSS linting and formatting stay clean.\n- Browser support tradeoffs are intentional, especially for newer CSS properties.\n- The README and source comments continue to credit relevant community work.\n- Consumers can quickly decide whether the reset fits their project and how to adapt it.\n\n## Non-Goals\n\n- This is not a CSS framework, component library, design system, or utility class toolkit.\n- This project should not prescribe a visual style, spacing scale, color system, typography scale, or layout system.\n- The reset should not grow into a broad compatibility layer or polyfill collection for every browser difference.\n- It should not hide opinionated application defaults behind the name of a reset.\n- It should not add build complexity unless distribution needs clearly require it.\n\n## Principles and Constraints\n\n- Prefer plain CSS that is easy to inspect over generated output.\n- Keep comments tied to rationale, browser support, or attribution rather than restating obvious declarations.\n- Treat accessibility, readability, and user-controlled settings as constraints, not optional enhancements.\n- Add new reset rules only when they solve a broadly shared authoring problem.\n- Be cautious with defaults that depend on project-specific custom properties or external CSS.\n- Preserve license clarity and attribution for all borrowed or adapted ideas.\n- Use MIT as the authoritative project license.\n- Avoid requiring consumer-defined custom properties for core reset behavior.\n\n## Current Focus\n\nThe repository currently centers on `reset.css`, with npm metadata, Prettier, Stylelint, and Dependabot supporting maintenance. Near-term work should keep the reset concise, improve documentation where usage expectations are unclear.\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-community-reset/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/css-custom-property-inspector.json b/src/data/project-doc-cache/css-custom-property-inspector.json new file mode 100644 index 00000000..44932cf2 --- /dev/null +++ b/src/data/project-doc-cache/css-custom-property-inspector.json @@ -0,0 +1,22 @@ +{ + "id": "css-custom-property-inspector", + "title": "css-custom-property-inspector", + "repoUrl": "https://github.com/schalkneethling/css-custom-property-inspector", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-custom-property-inspector/master/GOAL.md", + "status": "missing", + "statusCode": 404, + "content": "" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-custom-property-inspector/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/css-expect.json b/src/data/project-doc-cache/css-expect.json new file mode 100644 index 00000000..fdc830a9 --- /dev/null +++ b/src/data/project-doc-cache/css-expect.json @@ -0,0 +1,22 @@ +{ + "id": "css-expect", + "title": "css-expect", + "repoUrl": "https://github.com/schalkneethling/css-expect", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-expect/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\ncss-expect should make it practical to write small, trustworthy tests for native CSS custom functions, and eventually native CSS mixins, by asking a real browser for the computed result instead of emulating CSS in JavaScript.\n\n## Who This Is For\n\n- CSS library authors and design-system maintainers experimenting with native CSS custom functions.\n- Frontend engineers who want unit-test-style feedback for browser-evaluated CSS values.\n- Standards-aware tool builders who need diagnostics around emerging CSS features without introducing a parser, compiler, transpiler, or polyfill.\n\n## Core Goals\n\n- Provide a compact ESM API that can be used from Vitest, Vite+, Playwright-aware test suites, and other async JavaScript or TypeScript runners.\n- Evaluate CSS custom functions through real CSS properties and `getComputedStyle()`, so the selected browser engine remains the source of truth.\n- Support inline CSS and ordered CSS files so tests can cover both isolated snippets and reusable CSS modules.\n- Return clear expectation results and failure diagnostics, including generated CSS, browser details, feature support, expected values, and actual computed values.\n- Expose focused feature-support helpers for CSS functions, mixins, `@apply`, and available CSSOM metadata.\n\n## Success Looks Like\n\n- A user can install the package, load CSS, and assert a custom function result in a few lines of test code.\n- Tests fail because the browser computed a different value, not because css-expect guessed or reimplemented CSS semantics incorrectly.\n- Unsupported browser features produce actionable skip or failure diagnostics, depending on the configured policy.\n- The public API stays small, documented, typed, and stable enough for early adopters to use in real test suites.\n- Package checks, type checks, browser-backed tests, examples, and publishing checks pass before release.\n\n## Non-Goals\n\n- Not a CSS parser, compiler, transpiler, optimizer, polyfill, or way to make unsupported CSS features work in browsers that do not implement them.\n- Focused on testing CSS logic: custom function results now, and mixin return values when the platform supports them.\n- Not a replacement for Vitest, Playwright Test, or another test runner, and not a home for broad runner or framework-specific APIs.\n- Not a broad DOM assertion library, complete CSS feature-detection framework, or full suite of CSS assertions and expectations.\n- Avoids normalizing computed styles in ways that hide meaningful browser differences.\n- Defers large framework integrations until the core browser-backed expectation model is proven useful.\n\n## Principles and Constraints\n\n- The browser is authoritative. Prefer computed style and native CSSOM evidence over local interpretation.\n- Keep assertions property-aware. CSS custom functions are evaluated in a property grammar, so expectations should require callers to name the property under test.\n- Be explicit about experimental platform support. Chrome/Chromium is the currently validated target, while Firefox and WebKit options exist for future browser support.\n- Keep diagnostics useful for debugging test failures and emerging CSS feature support.\n- Keep the package install and publish path conservative: ESM output, generated types, ignored npm lifecycle scripts, pinned release checks, and trusted publishing.\n- Prefer a small dependency surface. Playwright is the runtime browser automation dependency; new dependencies should earn their place.\n\n## Current Focus\n\n- Validate the custom-function API against current Chromium support.\n- Preserve good behavior when browser support is missing by supporting both fail-fast and skip policies.\n- Keep examples and README usage aligned with the shipped API.\n- Prepare the package for a safe first public release.\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-expect/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/css-media-pseudo-polyfill.json b/src/data/project-doc-cache/css-media-pseudo-polyfill.json new file mode 100644 index 00000000..990bda27 --- /dev/null +++ b/src/data/project-doc-cache/css-media-pseudo-polyfill.json @@ -0,0 +1,22 @@ +{ + "id": "css-media-pseudo-polyfill", + "title": "css-media-pseudo-polyfill", + "repoUrl": "https://github.com/schalkneethling/css-media-pseudo-polyfill", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-media-pseudo-polyfill/master/GOAL.md", + "status": "missing", + "statusCode": 404, + "content": "" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-media-pseudo-polyfill/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/css-property-type-validator.json b/src/data/project-doc-cache/css-property-type-validator.json new file mode 100644 index 00000000..929b9ef9 --- /dev/null +++ b/src/data/project-doc-cache/css-property-type-validator.json @@ -0,0 +1,22 @@ +{ + "id": "css-property-type-validator", + "title": "css-property-type-validator", + "repoUrl": "https://github.com/schalkneethling/css-property-type-validator", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-property-type-validator/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\nCSS Property Type Validator exists to make typed CSS custom properties practical to adopt and maintain by catching invalid `@property` registrations and incompatible `var()` usage before those mistakes ship.\n\n## Who This Is For\n\n- CSS authors and design-system maintainers.\n- Teams adopting `@property` in existing CSS codebases and needing a low-risk migration path.\n- Tooling users who want the same validation available in CI, local command-line checks, Stylelint, VS Code-compatible editors, and a browser UI.\n- Integrators who need a stable TypeScript validation engine with machine-readable diagnostics.\n\n## Core Goals\n\n1. Provide a conservative standalone validation core that understands registered custom properties, validates required `@property` descriptors, and checks compatible usage through `var()`.\n2. Make typed custom property validation available wherever developers need it, while keeping behavior consistent through a shared core.\n3. Help existing projects adopt typed custom properties incrementally by generating reviewable `@property` registrations from concrete authored custom property declarations.\n4. Produce diagnostics that are clear for humans and stable for automation.\n5. Support real-world token architectures without assuming one global CSS shape.\n\n## Success Looks Like\n\n- A project can add the CLI or Stylelint plugin to CI and catch real typed-token mistakes without being flooded by false positives.\n- The same CSS produces compatible results across the core and every implementation built on it.\n- Generated `@property` output is conservative enough to review, useful enough to lower adoption friction, and transparent about values that need human judgment.\n- Diagnostics point users toward the invalid registration, assignment, fallback, unresolved import, or incompatible consuming property with enough context to fix it.\n- Supported CSS syntax data stays current enough to preserve trust in the validator as CSS evolves.\n\n## Non-Goals\n\n- Do not simulate the full browser cascade, DOM-specific computed values, or runtime custom property resolution.\n- Do not report diagnostics for ambiguous CSS patterns unless the validator can do so with high confidence.\n- Do not make unknown custom property checks mandatory or present them as full cascade analysis; they are opt-in static checks against configured token inputs.\n- Do not let package surfaces drift into separate behavior; new integrations should reuse the core rather than reimplementing validation.\n\n## Principles and Constraints\n\n- Prefer skipping uncertain cases over creating false positives.\n- Keep configuration explicit: registry inputs, token inputs, and unresolved-reference checks should be visible choices in each integration.\n- Keep generated registrations reviewable rather than magical.\n- Preserve stable machine-readable diagnostic fields for downstream tools.\n- Require spec-driven behavior and test coverage for both failing and compatible cases.\n- Keep manual release and package-publishing workflows auditable.\n- Target the maintained runtime baseline used by the repo, currently Node.js 22 or newer.\n\n## Current Focus\n\n- Improve support for custom property patterns that rely on whitespace or fallback toggles.\n- Reduce heuristic gaps in syntax compatibility checks where doing so remains conservative.\n- Make diagnostics more actionable by adding clearer remediation context.\n- Harden the Stylelint beta through real-project feedback.\n- Add config-file based registry discovery to reduce repeated CLI, VS Code, and Stylelint setup.\n- Expand validation to more languages, file types, and editor workflows when the core can model them safely.\n- Explore automatic fixes where remediation is clear, tested, and consistent across core-backed implementations.\n- Evaluate integrating `@property` generation into implementations beyond the CLI and browser UI where it fits the developer workflow.\n- Track how future CSS `@function` and mixin-like features should shape typed custom property validation.\n\n## Open Questions\n\n- How far should the generator go in explaining why each syntax was inferred before the output becomes too noisy?\n- Which additional integrations best satisfy the goal of making typed custom property validation available wherever developers need it?\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-property-type-validator/main/ROADMAP.md", + "status": "fetched", + "statusCode": 200, + "content": "# Roadmap\n\n## Current State\n\n- Validates `@property` syntax, `inherits`, and `initial-value` descriptors.\n- Validates authored values assigned directly to registered custom properties.\n- Supports declarations with multiple registered `var()` usages in one value.\n- Validates simple fallback branches in ordinary consuming declarations.\n- Assembles registrations from validation inputs, registry-only inputs, and local unconditioned imports.\n- Maintains a frozen list of supported syntax component names and checks it against the published spec.\n- Provides a first beta Stylelint plugin backed by the standalone core.\n\n## Near Term\n\n- Handle custom property patterns that rely on whitespace or fallback toggles.\n Example: account for the \"space toggle\" / `--foo: ;` pattern when validating assignments and `var(--foo, fallback)` usage.\n- Improve syntax compatibility checking beyond representative sample substitution where practical.\n Goal: reduce heuristic gaps for more complex syntax descriptors and consuming properties.\n- Expand diagnostics with clearer remediation guidance.\n Example: include more context about the registered syntax, the consuming property, and likely fixes.\n- Incorporate Stylelint beta feedback.\n Goal: harden the plugin once real projects exercise registry/token configuration, missing-path inputs, and large lint runs.\n- Add config-file based registry discovery.\n Goal: make repeated CLI, VS Code, and Stylelint usage easier for projects with shared token registries.\n\n## Later\n\n- Explore an ESLint CSS adapter once the standalone core and Stylelint behavior are stable.\n- Explore how typed CSS mixins and mixin-like patterns could strengthen the project's long-term value proposition.\n If CSS mixins land with typed parameters built on `@property`, this kind of validation becomes much closer to core tooling infrastructure.\n" + } + } +} diff --git a/src/data/project-doc-cache/css-tree-ast-viewer.json b/src/data/project-doc-cache/css-tree-ast-viewer.json new file mode 100644 index 00000000..aa925287 --- /dev/null +++ b/src/data/project-doc-cache/css-tree-ast-viewer.json @@ -0,0 +1,22 @@ +{ + "id": "css-tree-ast-viewer", + "title": "css-tree-ast-viewer", + "repoUrl": "https://github.com/schalkneethling/css-tree-ast-viewer", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-tree-ast-viewer/master/GOAL.md", + "status": "missing", + "statusCode": 404, + "content": "" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/css-tree-ast-viewer/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/jsconsole.json b/src/data/project-doc-cache/jsconsole.json new file mode 100644 index 00000000..cb7b269e --- /dev/null +++ b/src/data/project-doc-cache/jsconsole.json @@ -0,0 +1,22 @@ +{ + "id": "jsconsole", + "title": "jsconsole", + "repoUrl": "https://github.com/schalkneethling/jsconsole", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/jsconsole/master/GOAL.md", + "status": "missing", + "statusCode": 404, + "content": "" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/jsconsole/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/little-demos.json b/src/data/project-doc-cache/little-demos.json new file mode 100644 index 00000000..4a673580 --- /dev/null +++ b/src/data/project-doc-cache/little-demos.json @@ -0,0 +1,22 @@ +{ + "id": "little-demos", + "title": "little-demos", + "repoUrl": "https://github.com/schalkneethling/little-demos", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/little-demos/master/GOAL.md", + "status": "missing", + "statusCode": 404, + "content": "" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/little-demos/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/masonry-gridlanes-wc.json b/src/data/project-doc-cache/masonry-gridlanes-wc.json new file mode 100644 index 00000000..14033f3a --- /dev/null +++ b/src/data/project-doc-cache/masonry-gridlanes-wc.json @@ -0,0 +1,22 @@ +{ + "id": "masonry-gridlanes-wc", + "title": "masonry-gridlanes-wc", + "repoUrl": "https://github.com/schalkneethling/masonry-gridlanes-wc", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/masonry-gridlanes-wc/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\nMake CSS Grid Lanes masonry practical for real production websites today through a native-first Web Component that follows the platform when support exists and provides a small, spec-shaped fallback for unsupported browsers, without presenting itself as a masonry Grid Lanes polyfill.\n\n## Who This Is For\n\nThis project is for web developers who want to use CSS Grid Lanes-style masonry layouts in production today while keeping their markup, styling, and future migration path close to the native platform.\n\n## Core Goals\n\n1. Provide a production-ready light-DOM custom element.\n `` should be easy to register, style, and use with ordinary HTML and CSS. It should preserve author control over content, typography, and card design.\n\n2. Prefer the native platform whenever possible.\n When `display: grid-lanes` is supported, the component should get out of the browser's way and let native layout decide placement.\n\n3. Offer a spec-shaped JavaScript fallback for unsupported browsers.\n The fallback should preserve the supported Grid Lanes mental model: columns and rows, gaps, minimum track sizing, explicit row counts, order, flow tolerance, and simple grid-axis placement and spanning.\n\n4. Keep performance a first-class concern.\n Rich or mixed cards can use DOM measurement, while plain text collections can opt into Pretext-based height estimation. The headless API should support large text-card datasets and custom renderers without forcing all cards into the DOM.\n\n5. Use demos as living documentation and behavioral checks.\n The image, text, social-card, mixed-content, row-lane, reference-switch, and playground demos should show realistic authoring patterns and act as targets for Playwright integration tests.\n\n6. Make removal straightforward once native support is broad enough.\n An engineer or AI agent should be able to remove the library from a project in minutes, not hours, without changing the product's layout intent or user-facing presentation.\n\n7. Keep the package small, focused, and framework-independent.\n The library should remain usable from plain JavaScript and composable inside other frameworks without becoming tied to any one app stack.\n\n## Success Looks Like\n\n- Developers can install the package, register the component, add default styles, and get useful masonry behavior with minimal setup.\n- Production sites can rely on the component for supported column masonry, text-heavy layouts, and carefully authored row-lane layouts.\n- Native Grid Lanes support improves automatically without requiring authors to migrate away from the component, and eventual removal remains simple when browser support is sufficient.\n- Fallback behavior is covered by focused unit tests and Playwright integration tests that exercise the demo pages.\n- The README and demos clearly explain where the fallback is strong, where row mode is experimental, and where full CSS Grid parity is out of scope.\n- Performance-sensitive text layouts can reduce repeated DOM layout reads by using Pretext metrics or the headless API.\n\n## Non-Goals\n\n- This project is not a masonry Grid Lanes polyfill.\n- The fallback does not aim to support every `grid-template-*` form, every CSS Grid placement grammar edge case, or complete parity with native masonry Grid Lanes implementations.\n- The component should not become a framework-specific package, design system, feed renderer, or virtual scrolling library.\n- The demos should not define a required visual style for users; they exist to document and validate usage patterns.\n- Row mode should not pretend to solve every horizontal masonry case without author constraints. In current fallback behavior, row-lane layouts need deliberate sizing and scrolling patterns.\n- The package should not install or duplicate tools already provided by Vite+ in this repository.\n\n## Principles and Constraints\n\n- Native first: browser support is the preferred path, not an implementation detail to hide.\n- Progressive enhancement: unsupported browsers should get a useful supported subset instead of an unrelated layout model.\n- Easy exit: authoring patterns should stay close enough to native Grid Lanes that future removal is a small migration.\n- Light DOM by default: authors should be able to style children with ordinary selectors and keep semantic markup intact.\n- Clear scope over magical compatibility: document fallback guarantees honestly, especially while CSS Grid Lanes continues to evolve.\n- Measured performance work: use DOM measurement where needed, Pretext where appropriate, and pure layout math where possible.\n- Test public behavior: prioritize tests around exported layout helpers, custom-element fallback behavior, Pretext measurement, and demo workflows.\n- Use Vite+ for repository tasks: dependency management, checks, tests, and builds should go through `vp`.\n\n## Current Focus\n\n- Maintain the `0.1.x` foundation as a production-ready package with honest scope boundaries.\n- Strengthen column masonry and text-heavy layout confidence.\n- Improve row support while keeping it well documented and honest about its current constraints.\n- Preserve the demos as both examples and regression coverage for real-world usage.\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/masonry-gridlanes-wc/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/ossreleasefeed-v2.json b/src/data/project-doc-cache/ossreleasefeed-v2.json new file mode 100644 index 00000000..e901c720 --- /dev/null +++ b/src/data/project-doc-cache/ossreleasefeed-v2.json @@ -0,0 +1,22 @@ +{ + "id": "ossreleasefeed-v2", + "title": "ossreleasefeed-v2", + "repoUrl": "https://github.com/schalkneethling/ossreleasefeed-v2", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/ossreleasefeed-v2/master/GOAL.md", + "status": "missing", + "statusCode": 404, + "content": "" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/ossreleasefeed-v2/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/refined-plan-mode.json b/src/data/project-doc-cache/refined-plan-mode.json new file mode 100644 index 00000000..b9eeb521 --- /dev/null +++ b/src/data/project-doc-cache/refined-plan-mode.json @@ -0,0 +1,22 @@ +{ + "id": "refined-plan-mode", + "title": "refined-plan-mode", + "repoUrl": "https://github.com/schalkneethling/refined-plan-mode", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/refined-plan-mode/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\nRefined Plan Mode exists to make AI coding-agent plans reviewable before implementation by turning them into versioned, local artifacts with anchored browser feedback and an explicit approval gate. Think GitHub pull requests, but for plan mode.\n\n## Who This Is For\n\nRefined Plan Mode is for developers, maintainers, and workflow authors who want to review an agent's plan with the same care and intuitive convenience they expect from code review.\n\n## Core Goals\n\n1. Provide a reusable open-source plan-review workflow for multiple coding-agent harnesses.\n2. Keep the review loop local-first: plans, feedback, current state, and approvals live in a `.plan-review` directory in the target workspace.\n3. Make feedback precise by allowing reviewers to comment on individual lines, line ranges, and selected Markdown text.\n4. Preserve agent accountability by requiring agents to read feedback, revise plans into new versions, and wait for approval before implementation unless the user explicitly overrides that gate.\n5. Package the workflow in forms that agents can actually use: a browser reviewer app, portable instructions, and harness-specific integrations where they improve the experience.\n6. Keep plans self-contained enough for another agent or future session to understand the goal, assumptions, risks, validation, and recovery path.\n\n## Success Looks Like\n\n- A user can ask an agent to use Refined Plan Mode, review the generated plan in a browser, submit anchored feedback, and receive a revised plan without losing context.\n- Approval creates a clear artifact that the agent can execute deliberately.\n- The `.plan-review` file convention remains simple, inspectable, and portable across agent harnesses.\n- Integrations stay behaviorally consistent so the harness matters less over time.\n- The reviewer app remains fast and understandable for local use, with useful draft persistence and clear status when plan files are missing or stale.\n- New contributors can read the README, plugin skill, and this goal document and understand what belongs in the project.\n\n## Non-Goals\n\n- Refined Plan Mode is not a project management system, task tracker, or replacement for issues and pull requests.\n- It is not trying to host plans, synchronize feedback through a cloud service, or become a multi-user collaboration platform in its current form.\n- It should not hide or replace the agent's responsibility to reason about feedback, update the plan, and explain unsafe or conflicting requests.\n- It is not a general Markdown editor; editing plan text remains the agent's job, while the browser app focuses on review and feedback.\n- It should not become tightly coupled to one agent harness when the protocol can remain portable.\n- It should avoid heavyweight workflow machinery when a small file-based convention is enough.\n\n## Principles and Constraints\n\n- Local-first is a product principle, not only an implementation detail. Users should be able to inspect and recover the workflow by reading files.\n- The approval gate matters. Implementation should start only from an approved plan or an explicit user instruction to proceed.\n- Feedback should be structured enough for agents to consume reliably while remaining easy for humans to write.\n- The UI should feel like a focused review tool: quiet, dense, and practical rather than a planning dashboard.\n- The protocol should stay additive to each agent's normal planning guidance, avoid filling context with noise, and not replace the agent's existing judgment or safety practices.\n- Vite+ is the project toolchain. Use `vp install`, `vp check`, `vp test`, `vp dev`, and `vp build` rather than direct package-manager or tool invocations.\n\n## Current Focus\n\n- Solidify the review loop around versioned Markdown plans, anchored comments, JSON feedback, approval, and portable agent instructions.\n- Build a richer reviewer UI that makes plan review feel natural, efficient, and worth using repeatedly.\n- Keep installation and launch instructions clear for both local development and use from a separate target workspace.\n\n## Open Questions\n\n- How should Refined Plan Mode support OpenCode and Pi while keeping the protocol harness-agnostic?\n- How far can the reviewer be integrated into existing workflows before a packaged app, such as Electron, becomes the more practical path?\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/refined-plan-mode/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/skills-autoresearch-flue.json b/src/data/project-doc-cache/skills-autoresearch-flue.json new file mode 100644 index 00000000..f77f40f3 --- /dev/null +++ b/src/data/project-doc-cache/skills-autoresearch-flue.json @@ -0,0 +1,22 @@ +{ + "id": "skills-autoresearch-flue", + "title": "skills-autoresearch-flue", + "repoUrl": "https://github.com/schalkneethling/skills-autoresearch-flue", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/skills-autoresearch-flue/main/GOAL.md", + "status": "fetched", + "statusCode": 200, + "content": "# Project Goal\n\n## North Star\n\nBuild a small, auditable Flue-based autoresearch harness that helps maintainers decide when skill context is worth using, and what the smallest useful skill should contain.\n\nIn this project, autoresearch means a repeatable evidence loop: run evals and rubrics against chosen producer models, compare no-skill and skill-guided performance, inspect score feedback, and let a researcher agent propose the smallest useful change to the skill context when guidance is justified.\n\nA producer model is the model that does the actual task work from the provided input, reference material, and optional skill context. In day-to-day use, this could be any coding-agent model a team wants to evaluate, such as a Claude, GPT, Gemini, or other model release.\n\n## Who This Is For\n\nThis project is for engineers and teams who want to optimize how they use coding agents by measuring, against their own eval cases and rubrics, which skills are needed, which producer models need them, and how much guidance is worth carrying in context.\n\n## Core Goals\n\n1. Allow maintainers to determine whether any skill is needed by running deterministic eval cases and rubrics against the producer models they want to test.\n2. Allow maintainers to stop after a no-skill baseline when the model already meets or exceeds `target_score`, without investing more time or context in skill guidance.\n3. If a skill is needed, create, improve, or stress-test skill guidance so maintainers can choose between the whole skill, a narrower skill, or no skill.\n4. Preserve auditability through intuitive fixture inputs, baseline artifacts, candidate skills, output files, score JSON, summaries, cost summaries, and Flue transcripts.\n5. Support practical skill research workflows:\n - no-skill baseline checks,\n - seed-skill improvement,\n - seed-as-reference research,\n - regression testing existing skills against new producer models to decide whether a skill still improves performance, needs to be optimized, or should be removed.\n6. Support recovering from interrupted or failed runs without losing the audit trail or forcing maintainers to restart successful work.\n7. Prefer schema-validated structured data over ad hoc parsing for configs, model outputs, scores, and research patches.\n8. Keep fixtures small enough that contributors can understand failures, rerun smoke checks, and review generated artifacts by hand.\n\n## Success Looks Like\n\n- Engineers leave a run confident that any skill they add back to a project will meaningfully improve output quality against their evals and rubrics, and that removing an unnecessary skill will not cause a quality regression.\n- No-skill baseline runs can show when a model already meets `target_score` without adding a skill.\n- A run must stop before research when an imported or generated baseline already reaches `target_score`.\n- A candidate that reaches the aggregate target but regresses below baseline on an eval case must not be accepted as the final answer without making that regression explicit.\n- Candidate skill outcomes are evidence-based: create a new skill, improve the existing skill, narrow it, or remove it when no extra context is justified.\n- Interrupted or failed runs can be resumed or retried from the last trustworthy artifact without overwriting evidence from completed phases.\n- Artifacts are clear enough that a maintainer can reconstruct what changed, what output was produced, how it was scored, which role did the work, and why the run stopped.\n\n## Non-Goals\n\n- This is not a general-purpose benchmark suite for all agents or all tasks.\n- This should not treat more skill text as the default answer to a weak baseline; the right response may be better reference material, a different producer model, a narrower eval, a smaller skill, or no skill at all.\n- This should not collapse research, production, and judging into one self-scoring model call.\n- This should not optimize for large, opaque fixtures that are hard to inspect.\n- This should not silently overwrite generated evidence; exclusive artifact writes are part of the audit model.\n- This should not commit generated research iterations unless a test or document intentionally needs a recorded run.\n\n## Principles and Constraints\n\n- The harness is a TypeScript CLI built on the Flue agent framework; changes should preserve that architecture unless the project explicitly decides to replace it.\n- Researcher, producer, and judge responsibilities must stay separate: the researcher changes skill guidance, the producer writes eval outputs, and the judge scores only those outputs.\n- The harness should make costs and planned model calls visible before spending model-backed effort.\n- Baselines, candidate outputs, score rationales, summaries, and transcripts are evidence, so their paths and schemas should remain reviewable and documented.\n- Documentation, examples, tests, and schemas should stay aligned so maintainers are not guided by stale project behavior.\n- Research patches should make the smallest effective skill change that addresses observed score gaps and should avoid overfitting to one fixture.\n- Credentials are provided through Varlock and 1Password in the committed workflow; `.env`, resolved keys, and secret-bearing transcripts must not be committed.\n- Node, TypeScript ESM, pnpm, Valibot schemas, and the current fixture layout are part of the working alpha shape.\n\n## Current Focus\n\nThe current alpha is centered on `fixtures/projects/release-notes-alpha/`, a single-skill release-notes fixture with one eval case and an imported baseline. The committed workflow is designed to prove the end-to-end loop: load a project, import or generate a baseline, optionally research a candidate skill, run producer evals, score with an independent judge, aggregate results, and persist artifacts.\n\nNear-term work should keep tightening reliability, auditability, and documentation around that loop before broadening the surface area.\n\nCross-provider judging becomes active focus after the single-provider flow is stable enough to make comparisons meaningful.\n\n## Open Questions\n\n- How should resume and retry behavior preserve auditability while making failed model-backed runs less manual to recover?\n- What is the right project shape for first-class multi-skill research beyond running one target skill per invocation?\n" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/skills-autoresearch-flue/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/data/project-doc-cache/web-platform-pulse.json b/src/data/project-doc-cache/web-platform-pulse.json new file mode 100644 index 00000000..16a4def7 --- /dev/null +++ b/src/data/project-doc-cache/web-platform-pulse.json @@ -0,0 +1,22 @@ +{ + "id": "web-platform-pulse", + "title": "web-platform-pulse", + "repoUrl": "https://github.com/schalkneethling/web-platform-pulse", + "refreshedAt": "2026-07-06T17:36:18.744Z", + "docs": { + "goal": { + "filename": "GOAL.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/web-platform-pulse/master/GOAL.md", + "status": "missing", + "statusCode": 404, + "content": "" + }, + "roadmap": { + "filename": "ROADMAP.md", + "sourceUrl": "https://raw.githubusercontent.com/schalkneethling/web-platform-pulse/master/ROADMAP.md", + "status": "missing", + "statusCode": 404, + "content": "" + } + } +} diff --git a/src/lib/projectFilters.ts b/src/lib/projectFilters.ts new file mode 100644 index 00000000..60ca8f76 --- /dev/null +++ b/src/lib/projectFilters.ts @@ -0,0 +1,25 @@ +export type ProjectCategory = "main" | "demo"; + +type OrderedProject = { + data: { + category: ProjectCategory; + order: number; + }; +}; + +export function sortProjectsByOrder( + projects: readonly TProject[], +) { + return [...projects].sort((projectA, projectB) => { + return projectA.data.order - projectB.data.order; + }); +} + +export function getProjectsByCategory( + projects: readonly TProject[], + category: ProjectCategory, +) { + return sortProjectsByOrder( + projects.filter((project) => project.data.category === category), + ); +} diff --git a/src/lib/projectPages.ts b/src/lib/projectPages.ts new file mode 100644 index 00000000..7489d83a --- /dev/null +++ b/src/lib/projectPages.ts @@ -0,0 +1,16 @@ +type ProjectEntryLike = { + id: string; +}; + +export function getProjectPath(projectId: string) { + return `/projects/${projectId}`; +} + +export function getProjectStaticPaths( + projects: readonly ProjectEntry[], +) { + return projects.map((project) => ({ + params: { slug: project.id }, + props: { project }, + })); +} diff --git a/src/pages/projects.astro b/src/pages/projects.astro index 654ff562..149326be 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -1,41 +1,44 @@ --- import BaseLayout from "../layouts/BaseLayout.astro"; -import FeaturedProjects from "@components/Projects/FeaturedProjects.astro"; -import RandomProjects from "@components/Projects/RandomProjects.astro"; -import { fetchRandomProjects } from "../lib/github"; +import ProjectList from "@components/Projects/ProjectList.astro"; +import { getCollection } from "astro:content"; +import { getProjectsByCategory } from "../lib/projectFilters"; -const pageTitle = "Things I'm building"; +const pageTitle = "What I want to exist"; +const pageDescription = + "A curated collection of open source projects and web platform demos by Schalk Neethling."; -const FEATURED_NAMES = [ - "css-property-type-validator", - "css-media-pseudo-polyfill", - "linkstack", - "css-tree-ast-viewer", - "css-community-reset", - "schalkneethling.com", -]; - -const randomProjects = await fetchRandomProjects(FEATURED_NAMES, 7); +const projects = await getCollection("projects"); +const mainProjects = getProjectsByCategory(projects, "main"); +const demoProjects = getProjectsByCategory(projects, "demo"); --- - +

    {pageTitle}

    - I love making things. Whether it's a developer tool, a web component, a - polyfill, or a weekend experiment that takes on a life of its own — - building is how I learn and how I give back. Everything here is open source - because I still believe in the original ethos: build in public, share what - you make, and trust that others will do the same. + Some projects start as a tiny itch, some as a stubborn question, and some + because the web would feel a little better if they existed. This is a + deliberate list of the open source work I keep returning to.

    - Below you'll find projects I'm most invested in right now, plus a rotating - selection from the rest of my GitHub repos. If something catches your eye, - I'd love a star, a bug report, or a conversation about it. + The list is intentionally curated instead of random. Each card points to a + project page where I can tell the deeper story of what it is, why it + exists, and where it might go next.

    - - + +
    diff --git a/src/pages/projects/[slug].astro b/src/pages/projects/[slug].astro new file mode 100644 index 00000000..de4f882a --- /dev/null +++ b/src/pages/projects/[slug].astro @@ -0,0 +1,498 @@ +--- +import { getCollection } from "astro:content"; + +import BaseLayout from "../../layouts/BaseLayout.astro"; +import { getProjectStaticPaths } from "../../lib/projectPages"; + +export async function getStaticPaths() { + const projects = await getCollection("projects"); + + return getProjectStaticPaths(projects); +} + +const { project } = Astro.props; +const pageTitle = project.data.title; +const pageDescription = project.data.description; +const projectNumber = project.data.order.toString().padStart(2, "0"); +const technologies = project.data.technologies ?? []; +const whatAndWhy = project.data.whatAndWhy ?? project.data.description; +const goalSummary = + project.data.goalSummary ?? + "A fuller project goal summary is coming as this page is expanded from the repository docs."; +const currentState = + project.data.currentState ?? + "This project has a place in the curated list. The deeper status notes will be filled in as the starter project pages are completed."; +const nextSteps = project.data.nextSteps ?? [ + "Write the project-specific goal summary.", + "Connect the page to the repository documentation workflow.", +]; +const projectLinks = [ + { + href: project.data.repoUrl, + label: "Repository", + }, + ...(project.data.liveUrl + ? [ + { + href: project.data.liveUrl, + label: "Live project", + }, + ] + : []), + ...(project.data.goalDocUrl + ? [ + { + href: project.data.goalDocUrl, + label: "GOAL.md", + }, + ] + : []), + ...(project.data.roadmapDocUrl + ? [ + { + href: project.data.roadmapDocUrl, + label: "ROADMAP.md", + }, + ] + : []), +]; +--- + + +
    +
    + + +
    +
    +

    + Existence file + +

    +

    {pageTitle}

    +

    {whatAndWhy}

    +
    + + + + +
    + +
    +
    +

    Goal

    +

    {goalSummary}

    + { + technologies.length > 0 && ( +
      + {technologies.map((technology) =>
    • {technology}
    • )} +
    + ) + } +
    +
    + +
    +
    +

    Now

    +

    Where it is

    +

    {currentState}

    +
    + +
    +

    Next

    +

    What comes next

    +
      + {nextSteps.map((nextStep) =>
    • {nextStep}
    • )} +
    +
    +
    +
    +
    +
    + + diff --git a/tests/a11y.spec.ts b/tests/a11y.spec.ts index 89a4f12b..3d69d40b 100644 --- a/tests/a11y.spec.ts +++ b/tests/a11y.spec.ts @@ -15,7 +15,10 @@ for (const url of urls as TestUrl[]) { page, makeAxeBuilder, }, testInfo: TestInfo) => { - await page.goto(url.url, { waitUntil: "load" }); + const response = await page.goto(url.url, { waitUntil: "load" }); + + expect(response?.ok()).toBe(true); + await page.waitForLoadState("networkidle"); const axeBuilder = makeAxeBuilder(); diff --git a/tests/projectContent.test.ts b/tests/projectContent.test.ts new file mode 100644 index 00000000..2d781ede --- /dev/null +++ b/tests/projectContent.test.ts @@ -0,0 +1,146 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const projectsDirectory = path.join(process.cwd(), "src/content/projects"); + +const starterProjectIds = [ + "css-community-reset", + "ossreleasefeed-v2", + "web-platform-pulse", + "masonry-gridlanes-wc", + "css-expect", + "css-property-type-validator", + "create-project-calavera", + "css-benchpress", + "css-custom-property-inspector", + "common-components", + "css-media-pseudo-polyfill", + "skills-autoresearch-flue", + "refined-plan-mode", +]; + +const projectGoalDocs = new Map([ + [ + "masonry-gridlanes-wc", + "https://github.com/schalkneethling/masonry-gridlanes-wc/blob/main/GOAL.md", + ], + [ + "css-expect", + "https://github.com/schalkneethling/css-expect/blob/main/GOAL.md", + ], + [ + "css-property-type-validator", + "https://github.com/schalkneethling/css-property-type-validator/blob/main/GOAL.md", + ], + [ + "create-project-calavera", + "https://github.com/schalkneethling/create-project-calavera/blob/main/GOAL.md", + ], + [ + "css-benchpress", + "https://github.com/schalkneethling/css-benchpress/blob/main/GOAL.md", + ], + [ + "common-components", + "https://github.com/schalkneethling/common-components/blob/main/GOAL.md", + ], + [ + "skills-autoresearch-flue", + "https://github.com/schalkneethling/skills-autoresearch-flue/blob/main/GOAL.md", + ], + [ + "refined-plan-mode", + "https://github.com/schalkneethling/refined-plan-mode/blob/main/GOAL.md", + ], +]); + +const projectRoadmaps = new Map([ + [ + "css-property-type-validator", + "https://github.com/schalkneethling/css-property-type-validator/blob/main/ROADMAP.md", + ], + [ + "common-components", + "https://github.com/schalkneethling/common-components/blob/main/ROADMAP.md", + ], +]); + +const projectLiveUrls = new Map([ + [ + "css-property-type-validator", + "https://typedcss-validator.schalkneethling.com", + ], + ["create-project-calavera", "https://calavera.schalkneethling.com"], + ["common-components", "https://schalkneethling.github.io/common-components/"], +]); + +const readProjectFile = async (projectId: string) => + fs.readFile(path.join(projectsDirectory, `${projectId}.md`), "utf8"); + +const missingFrontmatterFields = (contents: string, fields: string[]) => + fields.filter((field) => !new RegExp(`^${field}:`, "m").test(contents)); + +describe("project content", () => { + it("defines imageUrl for every curated project card", async () => { + const filenames = await fs.readdir(projectsDirectory); + const projectFiles = filenames.filter((filename) => filename.endsWith(".md")); + const filesWithoutImages = []; + + for (const filename of projectFiles) { + const contents = await fs.readFile( + path.join(projectsDirectory, filename), + "utf8", + ); + + if (!/^imageUrl:\s+".+"/m.test(contents)) { + filesWithoutImages.push(filename); + } + } + + expect(filesWithoutImages).toEqual([]); + }); + + it("defines complete starter project detail content", async () => { + const requiredFields = [ + "whatAndWhy", + "goalSummary", + "currentState", + "nextSteps", + "contributionGuidance", + "technologies", + ]; + const incompleteProjects = []; + + for (const projectId of starterProjectIds) { + const contents = await readProjectFile(projectId); + const missingFields = missingFrontmatterFields(contents, requiredFields); + + if (missingFields.length > 0) { + incompleteProjects.push({ projectId, missingFields }); + } + } + + expect(incompleteProjects).toEqual([]); + }); + + it("links starter project docs and live URLs where they are currently available", async () => { + for (const [projectId, goalDocUrl] of projectGoalDocs) { + const contents = await readProjectFile(projectId); + + expect(contents).toContain(`goalDocUrl: "${goalDocUrl}"`); + } + + for (const [projectId, roadmapDocUrl] of projectRoadmaps) { + const contents = await readProjectFile(projectId); + + expect(contents).toContain(`roadmapDocUrl: "${roadmapDocUrl}"`); + } + + for (const [projectId, liveUrl] of projectLiveUrls) { + const contents = await readProjectFile(projectId); + + expect(contents).toContain(`liveUrl: "${liveUrl}"`); + } + }); +}); diff --git a/tests/projectDocCache.test.ts b/tests/projectDocCache.test.ts new file mode 100644 index 00000000..7236d769 --- /dev/null +++ b/tests/projectDocCache.test.ts @@ -0,0 +1,346 @@ +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { + refreshProjectDocCache, + repoUrlToRawDocUrl, +} from "../scripts/refresh-project-doc-cache.mjs"; + +const tempWorkspaceRoots: string[] = []; + +const projectFrontmatter = (project: { + id: string; + repoUrl: string; + title?: string; +}) => `--- +title: "${project.title ?? project.id}" +description: "Test project" +category: "main" +order: 1 +repoUrl: "${project.repoUrl}" +imageUrl: "https://example.com/${project.id}.png" +--- +`; + +async function createFixtureProject(project: { + contentDirectory: string; + id: string; + repoUrl: string; + title?: string; +}) { + await mkdir(project.contentDirectory, { recursive: true }); + await writeFile( + path.join(project.contentDirectory, `${project.id}.md`), + projectFrontmatter(project), + ); +} + +async function createTempWorkspace() { + const root = await mkdtemp(path.join(os.tmpdir(), "project-doc-cache-")); + tempWorkspaceRoots.push(root); + + return { + root, + contentDirectory: path.join(root, "src/content/projects"), + cacheDirectory: path.join(root, "src/data/project-doc-cache"), + }; +} + +afterEach(async () => { + await Promise.all( + tempWorkspaceRoots.splice(0).map((root) => + rm(root, { + force: true, + recursive: true, + }), + ), + ); +}); + +describe("repoUrlToRawDocUrl", () => { + it("builds raw GitHub URLs for project docs", () => { + expect( + repoUrlToRawDocUrl( + "https://github.com/schalkneethling/css-expect", + "GOAL.md", + ), + ).toBe( + "https://raw.githubusercontent.com/schalkneethling/css-expect/main/GOAL.md", + ); + }); + + it("rejects unsupported repository hosts", () => { + expect(() => + repoUrlToRawDocUrl( + "https://example.com/schalkneethling/css-expect", + "GOAL.md", + ), + ).toThrow("Unsupported repository host: example.com"); + }); + + it("rejects malformed GitHub repository paths", () => { + expect(() => + repoUrlToRawDocUrl("https://github.com/schalkneethling", "GOAL.md"), + ).toThrow( + "Invalid GitHub repository URL: https://github.com/schalkneethling", + ); + }); +}); + +describe("refreshProjectDocCache", () => { + it("writes fetched GOAL.md and ROADMAP.md content to the tracked cache", async () => { + const workspace = await createTempWorkspace(); + await createFixtureProject({ + contentDirectory: workspace.contentDirectory, + id: "css-expect", + repoUrl: "https://github.com/schalkneethling/css-expect", + }); + const fetch = vi.fn( + async (input: URL | RequestInfo, init?: RequestInit) => { + const url = String(input); + expect(init?.signal).toBeInstanceOf(AbortSignal); + + if (url.endsWith("/GOAL.md")) { + return new Response("# Goal\nMake CSS expectations readable."); + } + + return new Response("# Roadmap\n- Ship the assertion runner."); + }, + ); + + const summary = await refreshProjectDocCache({ + contentDirectory: workspace.contentDirectory, + cacheDirectory: workspace.cacheDirectory, + fetch, + }); + + expect(summary).toEqual({ + projects: 1, + fetched: 2, + missing: 0, + failed: 0, + }); + + const cache = JSON.parse( + await readFile( + path.join(workspace.cacheDirectory, "css-expect.json"), + "utf8", + ), + ); + + expect(cache).toMatchObject({ + id: "css-expect", + repoUrl: "https://github.com/schalkneethling/css-expect", + docs: { + goal: { + filename: "GOAL.md", + status: "fetched", + content: "# Goal\nMake CSS expectations readable.", + }, + roadmap: { + filename: "ROADMAP.md", + status: "fetched", + content: "# Roadmap\n- Ship the assertion runner.", + }, + }, + }); + expect(cache.refreshedAt).toMatch( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/, + ); + }); + + it("records missing docs without treating them as failures", async () => { + const workspace = await createTempWorkspace(); + await createFixtureProject({ + contentDirectory: workspace.contentDirectory, + id: "css-community-reset", + repoUrl: "https://github.com/schalkneethling/css-community-reset", + }); + const fetch = vi.fn( + async (_input: URL | RequestInfo, _init?: RequestInit) => + new Response("Not found", { status: 404 }), + ); + + const summary = await refreshProjectDocCache({ + contentDirectory: workspace.contentDirectory, + cacheDirectory: workspace.cacheDirectory, + fetch, + }); + + expect(summary).toEqual({ + projects: 1, + fetched: 0, + missing: 2, + failed: 0, + }); + + const cache = JSON.parse( + await readFile( + path.join(workspace.cacheDirectory, "css-community-reset.json"), + "utf8", + ), + ); + + expect(cache.docs.goal).toMatchObject({ + status: "missing", + statusCode: 404, + content: "", + }); + expect(cache.docs.roadmap).toMatchObject({ + status: "missing", + statusCode: 404, + content: "", + }); + }); + + it("records GitHub failures and continues writing cache files", async () => { + const workspace = await createTempWorkspace(); + await createFixtureProject({ + contentDirectory: workspace.contentDirectory, + id: "web-platform-pulse", + repoUrl: "https://github.com/schalkneethling/web-platform-pulse", + }); + const fetch = vi.fn( + async (_input: URL | RequestInfo, _init?: RequestInit) => { + throw new Error("GitHub is unavailable"); + }, + ); + + const summary = await refreshProjectDocCache({ + contentDirectory: workspace.contentDirectory, + cacheDirectory: workspace.cacheDirectory, + fetch, + }); + + expect(summary).toEqual({ + projects: 1, + fetched: 0, + missing: 0, + failed: 2, + }); + + const cache = JSON.parse( + await readFile( + path.join(workspace.cacheDirectory, "web-platform-pulse.json"), + "utf8", + ), + ); + + expect(cache.docs.goal).toMatchObject({ + status: "failed", + content: "", + error: "GitHub is unavailable", + }); + expect(cache.docs.roadmap).toMatchObject({ + status: "failed", + content: "", + error: "GitHub is unavailable", + }); + }); + + it("records invalid repository URLs as failed docs without aborting the refresh", async () => { + const workspace = await createTempWorkspace(); + await createFixtureProject({ + contentDirectory: workspace.contentDirectory, + id: "invalid-repo", + repoUrl: "https://example.com/schalkneethling/invalid-repo", + }); + const fetch = vi.fn(); + + const summary = await refreshProjectDocCache({ + contentDirectory: workspace.contentDirectory, + cacheDirectory: workspace.cacheDirectory, + fetch, + }); + + expect(summary).toEqual({ + projects: 1, + fetched: 0, + missing: 0, + failed: 2, + }); + expect(fetch).not.toHaveBeenCalled(); + + const cache = JSON.parse( + await readFile( + path.join(workspace.cacheDirectory, "invalid-repo.json"), + "utf8", + ), + ); + + expect(cache.docs.goal).toMatchObject({ + filename: "GOAL.md", + sourceUrl: "", + status: "failed", + content: "", + error: "Unsupported repository host: example.com", + }); + }); + + it("retries master before marking docs missing", async () => { + const workspace = await createTempWorkspace(); + await createFixtureProject({ + contentDirectory: workspace.contentDirectory, + id: "legacy-project", + repoUrl: "https://github.com/schalkneethling/legacy-project", + }); + const fetch = vi.fn( + async (input: URL | RequestInfo, _init?: RequestInit) => { + const url = String(input); + + if (url.includes("/main/")) { + return new Response("Not found", { status: 404 }); + } + + if (url.endsWith("/master/GOAL.md")) { + return new Response("# Goal\nLegacy default branch."); + } + + return new Response("Not found", { status: 404 }); + }, + ); + + const summary = await refreshProjectDocCache({ + contentDirectory: workspace.contentDirectory, + cacheDirectory: workspace.cacheDirectory, + fetch, + }); + + expect(summary).toEqual({ + projects: 1, + fetched: 1, + missing: 1, + failed: 0, + }); + expect(fetch).toHaveBeenCalledWith( + "https://raw.githubusercontent.com/schalkneethling/legacy-project/main/GOAL.md", + expect.objectContaining({ signal: expect.any(AbortSignal) }), + ); + expect(fetch).toHaveBeenCalledWith( + "https://raw.githubusercontent.com/schalkneethling/legacy-project/master/GOAL.md", + expect.objectContaining({ signal: expect.any(AbortSignal) }), + ); + + const cache = JSON.parse( + await readFile( + path.join(workspace.cacheDirectory, "legacy-project.json"), + "utf8", + ), + ); + + expect(cache.docs.goal).toMatchObject({ + status: "fetched", + sourceUrl: + "https://raw.githubusercontent.com/schalkneethling/legacy-project/master/GOAL.md", + content: "# Goal\nLegacy default branch.", + }); + expect(cache.docs.roadmap).toMatchObject({ + status: "missing", + sourceUrl: + "https://raw.githubusercontent.com/schalkneethling/legacy-project/master/ROADMAP.md", + statusCode: 404, + }); + }); +}); diff --git a/tests/projectFilters.test.ts b/tests/projectFilters.test.ts new file mode 100644 index 00000000..bab34fa5 --- /dev/null +++ b/tests/projectFilters.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; + +import { + getProjectsByCategory, + sortProjectsByOrder, +} from "../src/lib/projectFilters"; + +const projects = [ + { id: "third", data: { category: "main", order: 3 } }, + { id: "demo", data: { category: "demo", order: 1 } }, + { id: "first", data: { category: "main", order: 1 } }, + { id: "second", data: { category: "main", order: 2 } }, +] as const; + +describe("sortProjectsByOrder", () => { + it("sorts project entries by explicit order without mutating the input", () => { + const sorted = sortProjectsByOrder(projects); + + expect(sorted.map((project) => project.id)).toEqual([ + "demo", + "first", + "second", + "third", + ]); + expect(projects.map((project) => project.id)).toEqual([ + "third", + "demo", + "first", + "second", + ]); + }); +}); + +describe("getProjectsByCategory", () => { + it("returns only projects from the requested category in display order", () => { + expect( + getProjectsByCategory(projects, "main").map((project) => project.id), + ).toEqual(["first", "second", "third"]); + expect( + getProjectsByCategory(projects, "demo").map((project) => project.id), + ).toEqual(["demo"]); + }); +}); diff --git a/tests/projectPages.test.ts b/tests/projectPages.test.ts new file mode 100644 index 00000000..182bac72 --- /dev/null +++ b/tests/projectPages.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; + +import { getProjectPath, getProjectStaticPaths } from "../src/lib/projectPages"; + +const projects = [ + { id: "css-community-reset", data: { title: "css-community-reset" } }, + { id: "web-platform-pulse", data: { title: "web-platform-pulse" } }, +] as const; + +describe("getProjectPath", () => { + it("returns the internal detail page URL for a project id", () => { + expect(getProjectPath("css-community-reset")).toBe( + "/projects/css-community-reset", + ); + }); +}); + +describe("getProjectStaticPaths", () => { + it("maps project entries into Astro static paths with project props", () => { + expect(getProjectStaticPaths(projects)).toEqual([ + { + params: { slug: "css-community-reset" }, + props: { project: projects[0] }, + }, + { + params: { slug: "web-platform-pulse" }, + props: { project: projects[1] }, + }, + ]); + }); +}); diff --git a/tests/projectSkill.test.ts b/tests/projectSkill.test.ts new file mode 100644 index 00000000..27ab67b2 --- /dev/null +++ b/tests/projectSkill.test.ts @@ -0,0 +1,77 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const skillDirectory = path.join( + process.cwd(), + ".codex/skills/what-i-want-to-exist-projects", +); + +async function readSkillFile(relativePath: string) { + return fs.readFile(path.join(skillDirectory, relativePath), "utf8"); +} + +function parseFrontmatter(markdown: string) { + const match = markdown.match(/^---\n(?[\s\S]*?)\n---/); + + if (!match?.groups?.frontmatter) { + throw new Error("Missing frontmatter"); + } + + const frontmatter: Record = {}; + + for (const line of match.groups.frontmatter.split("\n")) { + const lineMatch = line.match(/^(?[a-z-]+):\s*(?.+)$/); + + if (lineMatch?.groups) { + frontmatter[lineMatch.groups.key] = lineMatch.groups.value; + } + } + + return frontmatter; +} + +describe("what-i-want-to-exist-projects skill", () => { + it("passes the core skill-creator frontmatter constraints", async () => { + const skill = await readSkillFile("SKILL.md"); + const frontmatter = parseFrontmatter(skill); + + expect(Object.keys(frontmatter).sort()).toEqual(["description", "name"]); + expect(frontmatter.name).toBe("what-i-want-to-exist-projects"); + expect(frontmatter.name).toMatch(/^[a-z0-9-]+$/); + expect(frontmatter.description).toBeTypeOf("string"); + expect(frontmatter.description).not.toMatch(/[<>]/); + expect(frontmatter.description.length).toBeLessThanOrEqual(1024); + }); + + it("defines trigger metadata for project content work", async () => { + const skill = await readSkillFile("SKILL.md"); + + expect(skill).toContain("name: what-i-want-to-exist-projects"); + expect(skill).toContain("src/content/projects/"); + expect(skill).toContain("src/data/project-doc-cache/"); + expect(skill).toContain("GOAL.md/ROADMAP.md"); + }); + + it("documents the repeatable project update workflow", async () => { + const skill = await readSkillFile("SKILL.md"); + + expect(skill).toContain("Cards link to internal `/projects/{slug}` pages."); + expect(skill).toContain("Every card needs an `imageUrl`."); + expect(skill).toContain("pnpm run refresh:project-docs"); + expect(skill).toContain("pnpm run test:a11y"); + }); + + it("includes project-entry reference details and UI metadata", async () => { + const reference = await readSkillFile("references/project-entry.md"); + const metadata = await readSkillFile("agents/openai.yaml"); + + expect(reference).toContain("Project entries live in"); + expect(reference).toContain("whatAndWhy"); + expect(reference).toContain("tests/projectContent.test.ts"); + expect(metadata).toContain("display_name: \"What I Want To Exist Projects\""); + expect(metadata).toContain( + "default_prompt: \"Use $what-i-want-to-exist-projects", + ); + }); +}); diff --git a/tests/projects.spec.ts b/tests/projects.spec.ts new file mode 100644 index 00000000..e4309582 --- /dev/null +++ b/tests/projects.spec.ts @@ -0,0 +1,95 @@ +import { expect, test } from "@playwright/test"; + +test("/projects keeps project cards visible after masonry upgrade", async ({ + page, +}) => { + await page.goto("/projects"); + + const firstProject = page.getByRole("heading", { + name: "css-community-reset", + }); + + await expect(firstProject).toBeVisible(); +}); + +test("/projects renders image headers for project cards", async ({ page }) => { + const transparentPng = Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + "base64", + ); + + await page.route( + /https:\/\/(opengraph\.githubassets\.com|repository-images\.githubusercontent\.com)\/.*/, + async (route) => { + await route.fulfill({ + body: transparentPng, + contentType: "image/png", + }); + }, + ); + + await page.goto("/projects"); + + const firstProjectImage = page + .locator("article.project-card") + .first() + .locator("img.project-card-image"); + + await expect(firstProjectImage).toBeVisible(); + await expect(firstProjectImage).toHaveAttribute( + "src", + /opengraph\.githubassets\.com|repository-images\.githubusercontent\.com/, + ); +}); + +test("project detail pages render project artifacts and links", async ({ + page, +}) => { + const transparentPng = Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + "base64", + ); + + await page.route( + /https:\/\/(opengraph\.githubassets\.com|repository-images\.githubusercontent\.com)\/.*/, + async (route) => { + await route.fulfill({ + body: transparentPng, + contentType: "image/png", + }); + }, + ); + + await page.goto("/projects/css-property-type-validator"); + + await expect( + page.getByRole("heading", { name: "css-property-type-validator" }), + ).toBeVisible(); + await expect( + page.getByRole("img", { name: "css-property-type-validator project card" }), + ).toBeVisible(); + await expect( + page.getByLabel("Technologies used").getByText("TypeScript"), + ).toBeVisible(); + await expect( + page.getByRole("link", { exact: true, name: "Repository" }), + ).toHaveAttribute( + "href", + "https://github.com/schalkneethling/css-property-type-validator", + ); + await expect( + page.getByRole("link", { exact: true, name: "Live project" }), + ).toHaveAttribute("href", "https://typedcss-validator.schalkneethling.com"); + await expect( + page.getByRole("link", { exact: true, name: "GOAL.md" }), + ).toHaveAttribute( + "href", + "https://github.com/schalkneethling/css-property-type-validator/blob/main/GOAL.md", + ); + await expect( + page.getByRole("link", { exact: true, name: "ROADMAP.md" }), + ).toHaveAttribute( + "href", + "https://github.com/schalkneethling/css-property-type-validator/blob/main/ROADMAP.md", + ); +}); diff --git a/tests/urls.json b/tests/urls.json index 52e2cac8..174d3c2f 100644 --- a/tests/urls.json +++ b/tests/urls.json @@ -23,6 +23,10 @@ "title": "Projects", "url": "/projects" }, + { + "title": "CSS Community Reset project", + "url": "/projects/css-community-reset" + }, { "title": "Build a profile page", "url": "/posts/build-a-profile-page-html-css-part6-dialog-form-netlify"