Files
ai-for-dummies/legacy/skills-review/app.js
T
Marcos Paulo 580293867d refactor: retire the hand-written site
Deletes the pre-Astro pages, scripts, and stylesheets that the migration
replaced, and moves the ones it did not replace out of the way.

Deleted (32 files): app.js, responsive.css, landing.css, rules/app.js,
rules/styles.css, skills/app.js, the ten route index.html files, and the
root hands-on/ copy, which is byte-identical to public/hands-on/ -- the
one the build actually ships.

Moved to legacy/ (12 files): styles.css, full-guide/audit.css,
chapters.css, skills/styles.css, skills-review/styles.css,
skills-review/change-lens.css, and the skills-review/app.js module graph.
These are not dead. The Astro pages import them and the build fails
without them, which the plan had not accounted for. They go to legacy/
rather than src/ because check-tokens.mjs sweeps src, and these files are
full of raw hex and unnamed breakpoints: moving one into src/ should mean
migrating it to tokens in the same change, not adding a scan exclusion.
The prettier, stylelint, and eslint ignore lists that already named these
files at their old paths now name legacy/ instead.

verify.mjs no longer reads app.js. The 102 Portuguese strings were
extracted from its translations.pt object before deletion into
.agents/snapshots/full-guide-pt.json -- a legacy capture, not a snapshot
of the Astro build, so the assertion still compares against an
independent source. The brace-matching helper's assertion is replaced by
one that rejects an empty snapshot entry, without which trimming the
snapshot would make the presence check pass vacuously. Count stays at 84.

audit-ui.mjs reads the ten pages from dist/ and resolves Astro's
base-absolute hrefs against it.

Before deleting anything, rendered-text-diff was run across all ten
routes plus both Portuguese pages: every one at parity, 0 missing and 0
extra. That comparison is not repeatable once the legacy files are gone.
computed-style-diff on /full-guide/ stays at 32 differences, so the moves
are style-neutral.

Docs updated to match: README, AGENTS.md, GATES.md, the architecture
context, the operations guide's lab instructions, and the three skills
that told you to serve the vanilla site.

Publishing is not part of this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 08:30:27 +00:00

546 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { catalog as legacyCatalog } from './catalog.js';
import { files } from './files.js';
import { renderVoteWidget } from './vote.js';
// The Astro review-desk route serializes the typed content collection before
// this client module loads. Keeping the legacy fallback preserves the vanilla
// page until cutover removes it.
const catalog = window.__SKILLS_REVIEW_CATALOG || legacyCatalog;
const state = {
selected: catalog[0],
query: '',
preview: 'original',
file: null,
sourceByPath: new Map(),
lens: false,
rendered: false,
diff: false,
searching: false,
contentMatches: new Set(),
searchTimer: null,
searchRequest: 0,
};
const $ = (selector) => document.querySelector(selector);
const escape = (value) =>
value.replace(
/[&<>"']/g,
(character) =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[character],
);
const redact = (value, entry) => {
const safe = value
.replace(/(NDO_PASS[^\n=]*[=:]\s*["']?)[^\n"']+/gi, '$1[REDACTED]')
.replace(/(password["']?\s*[:=]\s*["']?)[^\n"']+/gi, '$1[REDACTED]')
.replace(/\b[\w.+-]+@[\w.-]+\.[a-z]{2,}\b/gi, '[REDACTED SERVICE ACCOUNT]')
.replace(/\/home\/[A-Za-z0-9._-]+(?=\/)/g, '[REDACTED LOCAL USER]')
.replace(/display\/~[A-Za-z0-9._-]+/gi, 'display/~[REDACTED USER]');
return entry.id === 'ndo-repro'
? safe
.replace(/https?:\/\/[^\s)>]+/gi, '[REDACTED URL]')
.replace(/\b(?:[\w-]+\.)*netcracker\.[\w.-]+\b/gi, '[REDACTED HOST]')
.replace(/\bpedro[._ -]?aranha\b/gi, '[REDACTED CONTRIBUTOR]')
: safe;
};
const download = (name, content) => {
const url = URL.createObjectURL(new Blob([content], { type: 'text/markdown' }));
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
URL.revokeObjectURL(url);
};
const copy = async (content) => {
if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(content);
const textarea = document.createElement('textarea');
textarea.value = content;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.append(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
};
const packageFiles = (entry = state.selected) =>
files[entry.id] || [{ name: 'SKILL.md', path: entry.path, kind: 'skill' }];
const packageSearchText = (entry) =>
packageFiles(entry)
.map((file) => `${file.name} ${file.kind}`)
.join(' ');
function packageSummary(entry) {
const counts = packageFiles(entry).reduce((all, file) => {
all[file.kind] = (all[file.kind] || 0) + 1;
return all;
}, {});
const labels = {
skill: 'skill',
reference: 'ref',
script: 'script',
template: 'template',
data: 'data',
asset: 'asset',
};
return Object.entries(counts)
.map(([kind, count]) => `${count} ${labels[kind] || kind}${count === 1 ? '' : 's'}`)
.join(' · ');
}
const unchangedDraft = (file) =>
`# ${file.name}\n\n> Kept as-is in the improved package\n\nThis ${file.kind} file was not rewritten. Select **Change lens** to see why the improved draft concentrates its changes in the main skill contract.`;
const currentSource = () => state.sourceByPath.get(state.file.path);
const currentContent = () =>
state.preview === 'original'
? currentSource() || 'Loading original file…'
: state.file.improved ||
(state.file.name === 'SKILL.md'
? state.selected.improved
: currentSource()
? `# ${state.file.name}\n\n> Kept as-is in the improved package\n\n${currentSource()}`
: unchangedDraft(state.file));
const inlineMarkdown = (value) =>
escape(value)
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>')
.replace(
/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
'<a href="$2" target="_blank" rel="noreferrer">$1 ↗</a>',
);
const tableCells = (line) =>
line
.trim()
.replace(/^\||\|$/g, '')
.split('|')
.map((cell) => cell.trim());
const isTableDivider = (line) => /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
function markdownMarkup(markdown) {
const lines = markdown.replace(/\r/g, '').split('\n');
const headings = markdownHeadings(markdown);
let headingIndex = 0;
let index = 0;
const out = [];
if (lines[0] === '---') {
const end = lines.indexOf('---', 1);
if (end > 0) {
out.push(
`<dl class="markdown-frontmatter">${lines
.slice(1, end)
.map((line) => {
const [key, ...rest] = line.split(':');
return rest.length
? `<dt>${escape(key)}</dt><dd>${inlineMarkdown(rest.join(':').trim())}</dd>`
: '';
})
.join('')}</dl>`,
);
index = end + 1;
}
}
const startsBlock = (line, next) =>
!line ||
/^#{1,6}\s+/.test(line) ||
/^```/.test(line) ||
/^[-*+]\s+/.test(line) ||
/^\d+\.\s+/.test(line) ||
/^>\s?/.test(line) ||
/^---+$/.test(line) ||
(line.includes('|') && isTableDivider(next || ''));
while (index < lines.length) {
const line = lines[index];
if (!line.trim()) {
index += 1;
continue;
}
const heading = line.match(/^(#{1,6})\s+(.+)$/);
if (heading) {
const level = heading[1].length;
const item = headings[headingIndex++];
out.push(`<h${level} id="${item.id}">${inlineMarkdown(heading[2])}</h${level}>`);
index += 1;
continue;
}
if (/^```/.test(line)) {
const language = line.slice(3).trim();
const code = [];
index += 1;
while (index < lines.length && !/^```/.test(lines[index])) code.push(lines[index++]);
if (index < lines.length) index += 1;
out.push(
`<pre><code${language ? ` data-language="${escape(language)}"` : ''}>${escape(code.join('\n'))}</code></pre>`,
);
continue;
}
if (line.includes('|') && isTableDivider(lines[index + 1] || '')) {
const headings = tableCells(line);
index += 2;
const rows = [];
while (index < lines.length && lines[index].includes('|') && lines[index].trim())
rows.push(tableCells(lines[index++]));
out.push(
`<div class="markdown-table-wrap"><table><thead><tr>${headings.map((cell) => `<th>${inlineMarkdown(cell)}</th>`).join('')}</tr></thead><tbody>${rows.map((row) => `<tr>${headings.map((_, cell) => `<td>${inlineMarkdown(row[cell] || '')}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`,
);
continue;
}
const list = line.match(/^([-*+]|\d+\.)\s+(.+)$/);
if (list) {
const ordered = /\d+\./.test(list[1]);
const items = [];
while (index < lines.length) {
const item = lines[index].match(ordered ? /^\d+\.\s+(.+)$/ : /^[-*+]\s+(.+)$/);
if (!item) break;
items.push(`<li>${inlineMarkdown(item[1])}</li>`);
index += 1;
}
out.push(`<${ordered ? 'ol' : 'ul'}>${items.join('')}</${ordered ? 'ol' : 'ul'}>`);
continue;
}
if (/^>\s?/.test(line)) {
const quote = [];
while (index < lines.length && /^>\s?/.test(lines[index]))
quote.push(lines[index++].replace(/^>\s?/, ''));
out.push(`<blockquote>${inlineMarkdown(quote.join(' '))}</blockquote>`);
continue;
}
if (/^---+$/.test(line)) {
out.push('<hr>');
index += 1;
continue;
}
const paragraph = [line];
index += 1;
while (index < lines.length && !startsBlock(lines[index], lines[index + 1]))
paragraph.push(lines[index++]);
out.push(`<p>${inlineMarkdown(paragraph.join(' '))}</p>`);
}
return out.join('');
}
function markdownHeadings(markdown) {
const used = new Map();
let fenced = false;
return markdown
.replace(/\r/g, '')
.split('\n')
.flatMap((line) => {
if (/^```/.test(line)) {
fenced = !fenced;
return [];
}
const match = !fenced && line.match(/^(#{1,6})\s+(.+)$/);
if (!match) return [];
const text = match[2].replace(/[`*_\[\]]/g, '').trim();
const base =
text
.toLowerCase()
.replace(/[^\p{L}\p{N}]+/gu, '-')
.replace(/(^-|-$)/g, '') || 'section';
const seen = used.get(base) || 0;
used.set(base, seen + 1);
return [{ level: match[1].length, text, id: seen ? `${base}-${seen + 1}` : base }];
});
}
function markdownToc(markdown) {
const headings = markdownHeadings(markdown);
if (headings.length < 2) return '';
return `<nav class="markdown-toc" aria-label="On this page"><span>ON THIS PAGE</span><ol>${headings.map((heading) => `<li class="level-${heading.level}"><a href="#${heading.id}">${escape(heading.text)}</a></li>`).join('')}</ol></nav>`;
}
const changeRows = (entry) =>
entry.improve.map((why, index) => ({
kind: ['SAFETY', 'SCOPE', 'EVIDENCE', 'STRUCTURE'][index] || 'CLARITY',
before:
index === 0
? 'The submitted guidance leaves a material decision implicit.'
: 'The submitted package carries detail without a clear boundary.',
after:
index === 0
? 'The improved draft makes the operating rule explicit.'
: 'The improved draft moves the decision into a smaller, reviewable contract.',
why,
}));
function visible() {
return catalog.filter(
(item) =>
`${item.author} ${item.title} ${item.id} ${item.focus} ${packageSearchText(item)}`
.toLowerCase()
.includes(state.query) || state.contentMatches.has(item.id),
);
}
function syncUrl() {
const url = new URL(window.location.href);
url.searchParams.set('author', state.selected.author);
url.searchParams.set('skill', state.selected.id);
url.searchParams.set('view', state.preview);
if (state.file && state.file.name !== 'SKILL.md') url.searchParams.set('file', state.file.name);
else url.searchParams.delete('file');
if (state.preview === 'improved' && state.lens) url.searchParams.set('lens', 'changes');
else url.searchParams.delete('lens');
if (state.rendered) url.searchParams.set('render', 'preview');
else url.searchParams.delete('render');
if (state.diff) url.searchParams.set('compare', 'diff');
else url.searchParams.delete('compare');
history.replaceState({}, '', url);
}
function selectFromUrl() {
const params = new URLSearchParams(window.location.search);
const author = params.get('author');
const id = params.get('skill');
const view = params.get('view');
const byAuthor =
author && catalog.filter((item) => item.author.toLowerCase() === author.toLowerCase());
const byId = id && catalog.find((item) => item.id === id);
state.selected = byId || byAuthor?.[0] || catalog[0];
state.query = byAuthor ? state.selected.author.toLowerCase() : '';
state.preview = view === 'improved' ? 'improved' : 'original';
state.lens = state.preview === 'improved' && params.get('lens') === 'changes';
state.rendered = params.get('render') === 'preview';
state.diff = params.get('compare') === 'diff';
state.file = packageFiles().find((item) => item.name === params.get('file')) || packageFiles()[0];
$('#skill-filter').value = byAuthor ? state.selected.author : '';
$('#submission-count').textContent = `${catalog.length} submissions`;
}
function renderList() {
const items = visible();
$('#count').textContent = state.searching
? `Searching package files… ${items.length} of ${catalog.length}`
: `${items.length} of ${catalog.length} reviewed`;
$('#skill-list').innerHTML = items
.map(
(item) =>
`<button role="option" aria-selected="${item.id === state.selected.id}" class="${item.id === state.selected.id ? 'active' : ''}" data-id="${item.id}"><span>AUTHOR · ${escape(item.author)}</span><strong>${escape(item.title)}</strong><small>SKILL · ${escape(item.id)} · ${escape(item.status)}</small><em>${escape(packageSummary(item))}</em></button>`,
)
.join('');
$('#skill-list')
.querySelectorAll('button')
.forEach((button) => button.addEventListener('click', () => selectSkill(button.dataset.id)));
}
function selectSkill(id, focus = false) {
state.selected = catalog.find((item) => item.id === id) || state.selected;
state.file = packageFiles()[0];
state.preview = 'original';
state.lens = false;
state.rendered = false;
state.diff = false;
syncUrl();
renderList();
renderDetail();
loadSelectedFile();
if (focus) $('#skill-list').querySelector(`[data-id="${state.selected.id}"]`)?.focus();
}
async function fetchSource(entry, file) {
if (state.sourceByPath.has(file.path)) return state.sourceByPath.get(file.path);
try {
state.sourceByPath.set(file.path, redact(await (await fetch(file.path)).text(), entry));
} catch {
state.sourceByPath.set(
file.path,
'# Original preview unavailable\n\nServe this site from the repository root to load the submitted source.',
);
}
return state.sourceByPath.get(file.path);
}
async function loadSelectedFile() {
const entry = state.selected;
const file = state.file;
await fetchSource(entry, file);
if (state.selected.id === entry.id && state.file.path === file.path) renderDetail();
return state.sourceByPath.get(file.path);
}
function schedulePackageSearch() {
clearTimeout(state.searchTimer);
const query = state.query;
const request = ++state.searchRequest;
state.contentMatches.clear();
if (query.length < 3) {
state.searching = false;
renderList();
return;
}
state.searchTimer = setTimeout(async () => {
state.searching = true;
renderList();
await Promise.all(
catalog.flatMap((entry) => packageFiles(entry).map((file) => fetchSource(entry, file))),
);
if (request !== state.searchRequest) return;
state.contentMatches = new Set(
catalog
.filter((entry) =>
packageFiles(entry).some((file) =>
state.sourceByPath.get(file.path)?.toLowerCase().includes(query),
),
)
.map((entry) => entry.id),
);
state.searching = false;
renderList();
}, 180);
}
function lensMarkup(entry) {
return `<section class="change-lens" aria-label="Why this improved draft changed"><header><div><span>CHANGE LENS</span><h3>What changed — and why.</h3></div><button data-lens aria-pressed="true">Back to draft</button></header><p>The improved draft keeps the job, but narrows the decisions an agent must make from memory.</p><div class="change-rows">${changeRows(
entry,
)
.map(
(change, index) =>
`<article><span>0${index + 1} / ${change.kind}</span><div><b> Before</b><p>${escape(change.before)}</p></div><div><b>+ After</b><p>${escape(change.after)}</p></div><aside><b>Why</b><p>${escape(change.why)}</p></aside></article>`,
)
.join('')}</div></section>`;
}
function diffRows(before, after) {
const oldLines = before.split('\n');
const newLines = after.split('\n');
const rows = [];
let oldIndex = 0;
let newIndex = 0;
while (oldIndex < oldLines.length || newIndex < newLines.length) {
if (oldLines[oldIndex] === newLines[newIndex]) {
rows.push(
`<p class="same"><span>${oldIndex + 1}</span>${escape(oldLines[oldIndex] || '')}</p>`,
);
oldIndex += 1;
newIndex += 1;
continue;
}
const oldAhead = oldLines.slice(oldIndex + 1, oldIndex + 9).indexOf(newLines[newIndex]);
const newAhead = newLines.slice(newIndex + 1, newIndex + 9).indexOf(oldLines[oldIndex]);
if (
newIndex < newLines.length &&
(oldIndex >= oldLines.length || (oldAhead === -1 && newAhead !== -1) || newAhead < oldAhead)
) {
rows.push(`<p class="added"><span>+</span>${escape(newLines[newIndex++])}</p>`);
continue;
}
if (oldIndex < oldLines.length) {
rows.push(`<p class="removed"><span></span>${escape(oldLines[oldIndex++])}</p>`);
continue;
}
}
return rows.join('');
}
function diffMarkup(entry) {
if (state.file.name !== 'SKILL.md')
return `<section class="skill-diff" aria-label="Draft comparison"><header><div><span>PACKAGE DIFF</span><h3>Supporting file unchanged.</h3></div><button data-diff aria-pressed="true">Back to draft</button></header><p>This review only rewrites the main skill contract. The selected ${escape(state.file.kind)} file remains available in its original form.</p></section>`;
return `<section class="skill-diff" aria-label="Original and improved skill comparison"><header><div><span>SKILL DIFF</span><h3>Original → improved draft</h3></div><button data-diff aria-pressed="true">Back to draft</button></header><p>Green lines are additions; red lines are removals. Unmarked lines are shared context.</p><div class="diff-lines">${diffRows(currentSource() || 'Loading original Markdown…', entry.improved)}</div></section>`;
}
function previewMarkup(entry, available) {
if (state.preview === 'improved' && state.lens) return lensMarkup(entry);
if (state.diff) return diffMarkup(entry);
const label =
state.preview === 'original'
? 'ORIGINAL / SAFETY-REDACTED WHERE NEEDED'
: 'IMPROVED DRAFT / PACKAGE-AWARE';
const content = currentContent();
const body = state.rendered
? `<div class="markdown-preview" aria-label="Rendered Markdown preview">${markdownToc(content)}${markdownMarkup(content)}</div>`
: `<pre><code>${escape(content)}</code></pre>`;
return `<section class="preview"><header><div class="preview-title"><span>FILE PREVIEW</span><small>${label}</small></div><div>${state.preview === 'improved' ? '<button data-lens aria-pressed="false">Change lens</button>' : ''}<button data-diff aria-pressed="false">Diff</button><button class="preview-markdown" data-render aria-pressed="${state.rendered}">${state.rendered ? 'View source' : 'Preview Markdown'}</button><button data-copy>Copy</button><button data-download>Download</button></div></header><nav class="file-tabs" aria-label="Skill package files">${available.map((item) => `<button class="${item.name === state.file.name ? 'active' : ''}" data-file="${escape(item.name)}"><span>${escape(item.kind)}</span>${escape(item.name)}</button>`).join('')}</nav>${body}</section>`;
}
function renderDetail() {
const entry = state.selected;
const available = packageFiles(entry);
$('#detail').innerHTML =
`<header><div><span class="status">${escape(entry.status)}</span><h2>${escape(entry.title)}</h2><p>Submitted by <a class="author-link" href="?author=${encodeURIComponent(entry.author)}">${escape(entry.author)}</a> · <a class="share-link" href="?author=${encodeURIComponent(entry.author)}&skill=${encodeURIComponent(entry.id)}&view=${state.preview}">share review ↗</a></p></div><div class="switch" role="group" aria-label="Preview version"><button class="${state.preview === 'original' ? 'active' : ''}" data-preview="original">Original</button><button class="${state.preview === 'improved' ? 'active' : ''}" data-preview="improved">Improved draft</button></div></header><div class="purpose"><span>THE JOB</span><p>${escape(entry.focus)}</p></div><div id="vote-widget"></div><div class="review-grid"><section><span>WHAT'S ALREADY WORKING</span><ul>${entry.wins.map((item) => `<li>${escape(item)}</li>`).join('')}</ul></section><section><span>HIGHEST-VALUE IMPROVEMENTS</span><ul>${entry.improve.map((item) => `<li>${escape(item)}</li>`).join('')}</ul></section></div><aside class="extras"><span>GOOD NEXT ADDITION</span><p>${escape(entry.extras)}</p></aside>${previewMarkup(entry, available)}`;
renderVoteWidget($('#vote-widget'), entry.id);
$('#detail')
.querySelectorAll('[data-file]')
.forEach((button) =>
button.addEventListener('click', () => {
state.file = available.find((item) => item.name === button.dataset.file) || available[0];
state.rendered = false;
state.diff = false;
syncUrl();
renderDetail();
loadSelectedFile();
}),
);
$('#detail')
.querySelectorAll('[data-preview]')
.forEach((button) =>
button.addEventListener('click', () => {
state.preview = button.dataset.preview;
state.lens = false;
state.rendered = false;
state.diff = false;
syncUrl();
renderDetail();
loadSelectedFile();
}),
);
$('#detail')
.querySelectorAll('[data-lens]')
.forEach((button) =>
button.addEventListener('click', () => {
state.lens = !state.lens;
state.rendered = false;
state.diff = false;
syncUrl();
renderDetail();
}),
);
$('#detail')
.querySelectorAll('[data-diff]')
.forEach((button) =>
button.addEventListener('click', async () => {
await loadSelectedFile();
state.diff = !state.diff;
state.rendered = false;
state.lens = false;
syncUrl();
renderDetail();
}),
);
$('[data-render]')?.addEventListener('click', async () => {
await loadSelectedFile();
state.rendered = !state.rendered;
syncUrl();
renderDetail();
});
$('[data-copy]')?.addEventListener('click', async () => {
await loadSelectedFile();
await copy(currentContent());
$('[data-copy]').textContent = 'Copied';
});
$('[data-download]')?.addEventListener('click', async () => {
await loadSelectedFile();
download(
`${entry.id}-${state.file.name.replaceAll('/', '-')}-${state.preview}.md`,
currentContent(),
);
});
}
$('#skill-filter').addEventListener('input', (event) => {
state.query = event.target.value.toLowerCase().trim();
renderList();
schedulePackageSearch();
});
document.addEventListener('keydown', (event) => {
if (
event.metaKey ||
event.ctrlKey ||
event.altKey ||
/^(INPUT|TEXTAREA|SELECT)$/.test(document.activeElement?.tagName || '')
)
return;
const items = visible();
const current = items.findIndex((item) => item.id === state.selected.id);
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
event.preventDefault();
const offset = event.key === 'ArrowDown' ? 1 : -1;
selectSkill(items[(current + offset + items.length) % items.length]?.id, true);
}
if (event.key.toLowerCase() === 'p') {
event.preventDefault();
$('[data-render]')?.click();
}
});
window.addEventListener('popstate', () => {
selectFromUrl();
renderList();
renderDetail();
loadSelectedFile();
});
selectFromUrl();
renderList();
renderDetail();
loadSelectedFile();