Files
ai-for-dummies/src/content/config.ts
T
Marcos Paulo db4ae19c0a feat: add reviews content collection
Move all 24 review entries from skills-review/catalog.js +
skills-review/submitted-catalog.js into a typed Astro content collection at
src/content/reviews/. Each entry is a Markdown file with frontmatter for the
review metadata (id, author, focus, wins, improve, extras, name, description)
and a body that holds the 'improved' SKILL.md content.

Re-point scripts/build-skill-review.mjs at the new collection. The generator
reads each .md file, parses its YAML frontmatter, and writes
skill-reviews/improved/{id}/SKILL.md in the same shape the legacy catalog
produced — verified byte-identical via 'git diff --exit-code skill-reviews/'.

The 'name' field is preserved separately from 'id' because two entries
renamed the skill during review (id angular-accessibility-root → name
angular-accessibility; id confectionary-skill-hub → name confectionery-orders).
Without it the generator output would drift on those two files.

Does not yet delete skills-review/catalog.js or submitted-catalog.js —
verify.mjs and the legacy review-desk page both still read them, so they
stay as a mirror until task 16 rewires the page to the collection. Adding a
new submission today requires editing both the .md file (new source of
truth) and the legacy catalog.js (until task 16).

Done-when:
- 24 entries under src/content/reviews/ ✓
- verify.mjs's id:' count assertion still passes ✓
- git diff --exit-code skill-reviews/ clean after regenerating ✓
- astro check passes (22 files: 0 errors, 0 warnings, 2 hints) ✓

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-05 06:21:58 +00:00

172 lines
5.2 KiB
TypeScript

// Bilingual content schema for the Astro migration.
//
// Both `en` and `pt` are required on every localized field. A missing `pt`
// must fail the build — silent English fallback is how a bilingual site
// quietly becomes monolingual. Do not change `localized` to make a field
// optional without an explicit reason in a code review.
//
// Layout follows the brief in plans/astro-refactor/task-04-content-schema.md:
// - phases / providers / efforts / skillSources / handsOnPrompts /
// skillInstallPrompts — guide content carried over from app.js
// - chapters — copy for /models/, /agents/, /skills/, /summary/
// - reviews — the 24 entries from skills-review/catalog.js
//
// This file is schema only. Tasks 05 and 06 fill the entries; the content
// migrator agent owns this file end-to-end.
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
// One localized field. `en` and `pt` are mandatory strings; no nullish,
// no default. A missing `pt` is a build error, not a silent fallback.
const localized = z.object({ en: z.string(), pt: z.string() });
// Phases from `phases` in app.js: plan / build / review. Each phase has a
// model recommendation, title, prose, and a code-flavoured summary line.
const phases = defineCollection({
type: 'data',
schema: z.object({
id: z.string(),
model: localized,
title: localized,
copy: localized,
code: localized,
}),
});
// Providers from `modelGuide.providers` in app.js: openai / claude / gemini.
// Tiers are a 3-tuple of [kind label, name, note-localized].
const providers = defineCollection({
type: 'data',
schema: z.object({
id: z.string(),
label: z.string(),
source: z.string().url(),
title: localized,
copy: localized,
tiers: z.array(z.tuple([z.string(), z.string(), localized])),
config: z.string(),
}),
});
// Efforts from `modelGuide.efforts` in app.js: low / medium / high. Each
// effort carries three lines per locale (heading, body, code-line).
const efforts = defineCollection({
type: 'data',
schema: z.object({
id: z.string(),
en: z.array(z.string()),
pt: z.array(z.string()),
}),
});
// Skill source URLs from `skillSources` in app.js: pinned commit URLs for
// each public skill this site references.
const skillSources = defineCollection({
type: 'data',
schema: z.object({
id: z.string(),
url: z.string().url(),
}),
});
// Hands-on prompts from `handsOnPrompts` in app.js: per-locale basic and
// skills variants for the Tiny Tasks exercise.
const handsOnPrompts = defineCollection({
type: 'data',
schema: z.object({
id: z.string(),
en: z.object({ basic: z.string(), skills: z.string() }),
pt: z.object({ basic: z.string(), skills: z.string() }),
}),
});
// Skill install prompts from `skillInstallPrompts` in app.js: per-locale
// install instructions that name the pinned commits and the safety rules.
const skillInstallPrompts = defineCollection({
type: 'data',
schema: z.object({
id: z.string(),
en: z.string(),
pt: z.string(),
}),
});
// Chapter copy for /models/, /agents/, /skills/, /summary/. The body of
// each page composes one or more sections; the field names mirror what the
// existing HTML already says aloud (eyebrow / lede / card titles).
const chapters = defineCollection({
type: 'data',
schema: z.object({
id: z.string(),
eyebrow: localized,
title: localized,
lede: localized,
cards: z
.array(
z.object({
label: localized,
title: localized,
copy: localized,
}),
)
.optional(),
sections: z
.array(
z.object({
eyebrow: localized.optional(),
title: localized,
copy: localized.optional(),
steps: z
.array(
z.object({
label: localized,
copy: localized,
}),
)
.optional(),
}),
)
.optional(),
}),
});
// Review desk entries from skills-review/catalog.js +
// skills-review/submitted-catalog.js: 24 in total. Each entry is a Markdown
// file with frontmatter for the metadata and a body that holds the
// `improved` SKILL.md content. Astro renders the body via `<Content />` and
// exposes the raw markdown via `entry.body` for the review desk's diff view.
//
// `name` is preserved separately from `id`: two entries renamed the skill
// during review (id `angular-accessibility-root` → name
// `angular-accessibility`, id `confectionary-skill-hub` → name
// `confectionery-orders`). The build script uses `name` to keep
// `skill-reviews/improved/**/SKILL.md` byte-identical.
const reviews = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/reviews' }),
schema: z.object({
id: z.string(),
author: z.string(),
path: z.string(),
title: z.string(),
status: z.string(),
focus: z.string(),
wins: z.array(z.string()),
improve: z.array(z.string()),
extras: z.string(),
name: z.string(),
description: z.string(),
}),
});
export const collections = {
phases,
providers,
efforts,
skillSources,
handsOnPrompts,
skillInstallPrompts,
chapters,
reviews,
};