83c956c2b6
The seven selector buttons were rendering `kind` and `use`, which belong to the detail panel. ponytail's button read "SIMPLIFICATION INSTINCT" and a full paragraph of prose where today's page reads "SIMPLIFY" and "minimum code that holds". English was wrong, not just Portuguese missing, on all seven. Those two lines are not in `interactiveCopy`, which is why task 05b had nothing to migrate them from: the English lives in the `full-guide/index.html` markup and the Portuguese in `translations.pt`. Adds `label` and `tagline` to the `commonSkills` schema and to all seven entries, both taken verbatim from those two sources, and points the buttons at them. The panel keeps reading `kind` and `use`. This closes the last 13 of the 102 `translations.pt` entries. Coverage in `dist/full-guide/index.html` is now 102 of 102. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
307 lines
9.8 KiB
TypeScript
307 lines
9.8 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/ and the landing
|
|
// route map. 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).
|
|
//
|
|
// `cards[].href` and `cards[].cta` were added in task 04b: the landing
|
|
// route map's six cards have non-uniform call-to-action text (four read
|
|
// "Open chapter →", one "Open lab →", one "Open desk →"), so a single
|
|
// hard-coded default would be wrong. `href` is the destination; `cta` is
|
|
// the localized link text.
|
|
//
|
|
// `sections[].panelLabel` / `panelCode` / `panelHint` carry the highlighted
|
|
// rule panels on /models/, /agents/, and /skills/ (the routing rule, the
|
|
// MAIN / ORCHESTRATOR tree, and the package-anatomy hint).
|
|
//
|
|
// `threadLabel` / `threadText` and `footer` carry the landing-page thread
|
|
// strip and footer line.
|
|
const chapters = defineCollection({
|
|
type: 'data',
|
|
schema: z.object({
|
|
id: z.string(),
|
|
eyebrow: localized,
|
|
title: localized,
|
|
lede: localized,
|
|
threadLabel: localized.optional(),
|
|
threadText: localized.optional(),
|
|
footer: localized.optional(),
|
|
cards: z
|
|
.array(
|
|
z.object({
|
|
label: localized,
|
|
title: localized,
|
|
copy: localized,
|
|
href: z.string().optional(),
|
|
cta: localized.optional(),
|
|
}),
|
|
)
|
|
.optional(),
|
|
sections: z
|
|
.array(
|
|
z.object({
|
|
eyebrow: localized.optional(),
|
|
title: localized,
|
|
copy: localized.optional(),
|
|
panelLabel: localized.optional(),
|
|
panelCode: localized.optional(),
|
|
panelHint: localized.optional(),
|
|
steps: z
|
|
.array(
|
|
z.object({
|
|
label: localized,
|
|
copy: localized,
|
|
}),
|
|
)
|
|
.optional(),
|
|
}),
|
|
)
|
|
.optional(),
|
|
}),
|
|
});
|
|
|
|
// Workers from `interactiveCopy.workers` in app.js: the three sub-agent
|
|
// cards on /full-guide/. Each entry holds a 3-element array per locale
|
|
// (`[label, detail, result]`), not a localized object — the renderer
|
|
// unwraps it directly: `worker[language()][0..2]`.
|
|
const workers = defineCollection({
|
|
type: 'data',
|
|
schema: z.object({
|
|
id: z.string(),
|
|
en: z.array(z.string()),
|
|
pt: z.array(z.string()),
|
|
}),
|
|
});
|
|
|
|
// Trees from `interactiveCopy.trees` in app.js: the four worktree nodes
|
|
// (main / ui / tests / docs). `owner` and `note` are localized; `path`
|
|
// and `command` are single shell strings (the git command shown to the
|
|
// user). `status` is rendered by index.html, not the GuideSelector island,
|
|
// but it ships in the data and the schema keeps it so nothing is dropped.
|
|
const trees = defineCollection({
|
|
type: 'data',
|
|
schema: z.object({
|
|
id: z.string(),
|
|
status: z.string(),
|
|
owner: localized,
|
|
path: z.string(),
|
|
command: z.string(),
|
|
note: localized,
|
|
}),
|
|
});
|
|
|
|
// Routes from `interactiveCopy.routes` in app.js: plan / build / explore /
|
|
// review. `score` is the numeric reasoning-load value (rendered into
|
|
// `style="--score:N%"`), `label` and `why` are localized.
|
|
const routes = defineCollection({
|
|
type: 'data',
|
|
schema: z.object({
|
|
id: z.string(),
|
|
score: z.number(),
|
|
label: localized,
|
|
why: localized,
|
|
}),
|
|
});
|
|
|
|
// Skill files from `interactiveCopy.skillFiles` in app.js: SKILL.md /
|
|
// references/ / scripts/ / assets/. `icon` and `title` are unlocalized
|
|
// labels; `en` and `pt` are separate top-level strings, not a localized
|
|
// object — matching how `renderSkillFile` reads them.
|
|
const skillFiles = defineCollection({
|
|
type: 'data',
|
|
schema: z.object({
|
|
id: z.string(),
|
|
icon: z.string(),
|
|
title: z.string(),
|
|
en: z.string(),
|
|
pt: z.string(),
|
|
}),
|
|
});
|
|
|
|
// Skill workflow from `interactiveCopy.skillWorkflow` in app.js: the five
|
|
// steps of building a skill (observe / trigger / scaffold / write /
|
|
// validate). `number` is unlocalized; `title` / `question` / `action` /
|
|
// `output` / `proof` are all localized.
|
|
const skillWorkflow = defineCollection({
|
|
type: 'data',
|
|
schema: z.object({
|
|
id: z.string(),
|
|
number: z.string(),
|
|
title: localized,
|
|
question: localized,
|
|
action: localized,
|
|
output: localized,
|
|
proof: localized,
|
|
}),
|
|
});
|
|
|
|
// Common skills from `interactiveCopy.commonSkills` in app.js: the seven
|
|
// shared skill cards on /full-guide/. `number` and `title` are unlocalized;
|
|
// `kind` / `rule` / `use` / `example` / `caution` are localized. `source`
|
|
// is the GitHub URL — in app.js it is read from `skillSources[id]` at
|
|
// render time; here it is embedded to match the GuideSelectorData
|
|
// contract. If the URL changes, both this entry and the matching
|
|
// `skillSources/<id>.json` must be updated.
|
|
//
|
|
// `label` and `tagline` are the selector *button*'s two lines and are
|
|
// distinct from `kind` and `use`, which belong to the detail panel:
|
|
// ponytail's button reads "SIMPLIFY" / "minimum code that holds" while its
|
|
// panel reads "SIMPLIFICATION INSTINCT" and a paragraph of prose. They are
|
|
// not in `interactiveCopy` — the English lives in the `full-guide/index.html`
|
|
// markup and the Portuguese in `translations.pt` — so task 05b had nothing
|
|
// to migrate them from and the page rendered `kind`/`use` on the buttons.
|
|
const commonSkills = defineCollection({
|
|
type: 'data',
|
|
schema: z.object({
|
|
id: z.string(),
|
|
number: z.string(),
|
|
kind: localized,
|
|
label: localized,
|
|
tagline: localized,
|
|
title: z.string(),
|
|
rule: localized,
|
|
use: localized,
|
|
example: localized,
|
|
caution: localized,
|
|
source: z.string().url(),
|
|
}),
|
|
});
|
|
|
|
// 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,
|
|
workers,
|
|
trees,
|
|
routes,
|
|
skillFiles,
|
|
skillWorkflow,
|
|
commonSkills,
|
|
reviews,
|
|
};
|