fix: Optimize docs page API (7MB→19KB), fix categories, add MI translations

- Add summary projection to Document.list() excluding heavy content fields
- Fix 23 documents with invalid categories (framework/governance/reference)
- Archive 9 duplicate documents (kept canonical short-slug versions)
- Add Te Reo Māori UI translations to docs-app.js and document-cards.js
- Refactor language checks to be extensible (no more hardcoded EN/DE/FR)
- Remove unused font preloads from docs.html (fixes browser warning)
- Add resources to valid publish categories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
TheFlow 2026-02-11 10:14:02 +13:00
parent 28cb1139a9
commit 0ef20a6e8d
5 changed files with 84 additions and 35 deletions

View file

@ -38,10 +38,6 @@
<meta name="apple-mobile-web-app-title" content="Tractatus">
<link rel="apple-touch-icon" href="/images/tractatus-icon-new.svg">
<!-- Preload critical fonts for faster initial render -->
<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/inter-700.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/css/fonts.css?v=0.1.2.1770750464740">
<link rel="stylesheet" href="/css/tailwind.css?v=0.1.2.1770750464740">
<link rel="stylesheet" href="/css/tractatus-theme.min.css?v=0.1.2.1770750464740">

View file

@ -41,6 +41,14 @@ class DocumentCards {
practical: 'Guides pratiques',
technical: 'Détails techniques',
reference: 'Documentation de référence'
},
mi: {
title: 'Aratohu Tae',
critical: 'Ngā wāhanga waipuke',
conceptual: 'Ngā whakamāramatanga ariā',
practical: 'Ngā aratohu whaihua',
technical: 'Ngā taipitopito hangarau',
reference: 'Ngā tuhinga tohutoro'
}
};

View file

@ -82,6 +82,32 @@ const UI_TRANSLATIONS = {
'advanced-topics': 'Sujets avancés',
'business-leadership': 'Business & Leadership'
}
},
mi: {
pageTitle: 'Ngā Tuhinga Anga',
pageSubtitle: 'Ngā tautukunga hangarau, ngā aratohu, me ngā rauemi tohutoro',
documentsHeading: 'Ngā Tuhinga',
searchButton: 'Rapu',
backToDocuments: 'Hoki ki ngā Tuhinga',
selectDocument: 'Kōwhiria he Tuhinga',
selectDocumentDesc: 'Kōwhiria he tuhinga mai i te taha ki te tīmata pānui',
loadingDocument: 'Kei te uta te tuhinga...',
errorLoadingDoc: 'He hapa i te utanga o te tuhinga',
tableOfContents: 'Rārangi Kaupapa',
downloadPdf: 'Tikiake PDF',
github: 'GitHub',
publicRepository: 'Pūtahi Tūmatanui',
publicRepositoryDesc: 'Waehere pūtake, tauira me ngā koha',
readmeQuickStart: 'README & Tīmata Tere',
readmeQuickStartDesc: 'Aratohu whakauru me te tīmata',
categories: {
'getting-started': 'Tīmata',
'resources': 'Ngā Rauemi',
'research-theory': 'Rangahau & Ariā',
'technical-reference': 'Tohutoro Hangarau',
'advanced-topics': 'Kaupapa Matatau',
'business-leadership': 'Pakihi & Hautūtanga'
}
}
};
@ -95,17 +121,15 @@ function updatePageUI(lang = currentLanguage) {
const t = getUITranslations(lang);
// Update page header
const allPageTitles = Object.values(UI_TRANSLATIONS).map(t => t.pageTitle);
const pageTitle = document.querySelector('h1');
if (pageTitle && pageTitle.textContent === UI_TRANSLATIONS.en.pageTitle ||
pageTitle.textContent === UI_TRANSLATIONS.de.pageTitle ||
pageTitle.textContent === UI_TRANSLATIONS.fr.pageTitle) {
if (pageTitle && allPageTitles.includes(pageTitle.textContent)) {
pageTitle.textContent = t.pageTitle;
}
const allPageSubtitles = Object.values(UI_TRANSLATIONS).map(t => t.pageSubtitle);
const pageSubtitle = document.querySelector('.text-gray-600.mt-2');
if (pageSubtitle && (pageSubtitle.textContent === UI_TRANSLATIONS.en.pageSubtitle ||
pageSubtitle.textContent === UI_TRANSLATIONS.de.pageSubtitle ||
pageSubtitle.textContent === UI_TRANSLATIONS.fr.pageSubtitle)) {
if (pageSubtitle && allPageSubtitles.includes(pageSubtitle.textContent)) {
pageSubtitle.textContent = t.pageSubtitle;
}
@ -116,20 +140,17 @@ function updatePageUI(lang = currentLanguage) {
}
// Update sidebar Documents heading
const allDocsHeadings = Object.values(UI_TRANSLATIONS).map(t => t.documentsHeading);
const docsHeading = document.querySelector('aside h3');
if (docsHeading && (docsHeading.textContent === UI_TRANSLATIONS.en.documentsHeading ||
docsHeading.textContent === UI_TRANSLATIONS.de.documentsHeading ||
docsHeading.textContent === UI_TRANSLATIONS.fr.documentsHeading)) {
if (docsHeading && allDocsHeadings.includes(docsHeading.textContent)) {
docsHeading.textContent = t.documentsHeading;
}
// Update GitHub section heading
const allGithubLabels = Object.values(UI_TRANSLATIONS).map(t => t.github);
const githubHeadings = document.querySelectorAll('aside h3');
githubHeadings.forEach(heading => {
if (heading.textContent.trim() === UI_TRANSLATIONS.en.github ||
heading.textContent.trim() === UI_TRANSLATIONS.de.github ||
heading.textContent.trim() === UI_TRANSLATIONS.fr.github) {
// Keep the SVG icon, just update text
if (allGithubLabels.includes(heading.textContent.trim())) {
const textNode = Array.from(heading.childNodes).find(node => node.nodeType === Node.TEXT_NODE);
if (textNode) {
textNode.textContent = t.github;
@ -138,31 +159,28 @@ function updatePageUI(lang = currentLanguage) {
});
// Update GitHub links
const allRepoTitles = Object.values(UI_TRANSLATIONS).map(t => t.publicRepository);
const allReadmeTitles = Object.values(UI_TRANSLATIONS).map(t => t.readmeQuickStart);
const allRepoDescs = Object.values(UI_TRANSLATIONS).map(t => t.publicRepositoryDesc);
const allReadmeDescs = Object.values(UI_TRANSLATIONS).map(t => t.readmeQuickStartDesc);
const githubLinks = document.querySelectorAll('aside a[href*="github.com"]');
githubLinks.forEach(link => {
const titleDiv = link.querySelector('.text-sm.font-medium');
const descDiv = link.querySelector('.text-xs.text-gray-500');
if (titleDiv) {
if (titleDiv.textContent === UI_TRANSLATIONS.en.publicRepository ||
titleDiv.textContent === UI_TRANSLATIONS.de.publicRepository ||
titleDiv.textContent === UI_TRANSLATIONS.fr.publicRepository) {
if (allRepoTitles.includes(titleDiv.textContent)) {
titleDiv.textContent = t.publicRepository;
} else if (titleDiv.textContent === UI_TRANSLATIONS.en.readmeQuickStart ||
titleDiv.textContent === UI_TRANSLATIONS.de.readmeQuickStart ||
titleDiv.textContent === UI_TRANSLATIONS.fr.readmeQuickStart) {
} else if (allReadmeTitles.includes(titleDiv.textContent)) {
titleDiv.textContent = t.readmeQuickStart;
}
}
if (descDiv) {
if (descDiv.textContent === UI_TRANSLATIONS.en.publicRepositoryDesc ||
descDiv.textContent === UI_TRANSLATIONS.de.publicRepositoryDesc ||
descDiv.textContent === UI_TRANSLATIONS.fr.publicRepositoryDesc) {
if (allRepoDescs.includes(descDiv.textContent)) {
descDiv.textContent = t.publicRepositoryDesc;
} else if (descDiv.textContent === UI_TRANSLATIONS.en.readmeQuickStartDesc ||
descDiv.textContent === UI_TRANSLATIONS.de.readmeQuickStartDesc ||
descDiv.textContent === UI_TRANSLATIONS.fr.readmeQuickStartDesc) {
} else if (allReadmeDescs.includes(descDiv.textContent)) {
descDiv.textContent = t.readmeQuickStartDesc;
}
}

View file

@ -30,6 +30,9 @@ async function listDocuments(req, res) {
filter.audience = audience;
}
// Use summary projection for list endpoint (returns ~7KB instead of ~7MB)
const summary = req.query.fields !== 'full';
if (quadrant && !audience) {
documents = await Document.findByQuadrant(quadrant, {
limit: parseInt(limit),
@ -48,7 +51,8 @@ async function listDocuments(req, res) {
documents = await Document.list({
limit: parseInt(limit),
skip: parseInt(skip),
filter
filter,
summary
});
total = await Document.count(filter);
}

View file

@ -223,8 +223,8 @@ class Document {
// Validate category is in allowed list
const validCategories = [
'getting-started', 'technical-reference', 'research-theory',
'advanced-topics', 'case-studies', 'business-leadership', 'archives'
'getting-started', 'resources', 'research-theory', 'technical-reference',
'advanced-topics', 'business-leadership', 'archives'
];
if (!validCategories.includes(category)) {
return {
@ -310,13 +310,36 @@ class Document {
/**
* List all documents
* @param {object} options
* @param {boolean} options.summary - If true, return only summary fields (for sidebar/listing)
*/
static async list(options = {}) {
const collection = await getCollection('documents');
const { limit = 50, skip = 0, sort = { order: 1, 'metadata.date_created': -1 }, filter = {} } = options;
const { limit = 50, skip = 0, sort = { order: 1, 'metadata.date_created': -1 }, filter = {}, summary = false } = options;
return await collection
.find(filter)
const query = collection.find(filter);
if (summary) {
query.project({
title: 1,
slug: 1,
category: 1,
order: 1,
visibility: 1,
quadrant: 1,
persistence: 1,
audience: 1,
download_formats: 1,
'metadata.date_created': 1,
'metadata.date_updated': 1,
'metadata.version': 1,
'translations.de.title': 1,
'translations.fr.title': 1,
'translations.mi.title': 1
});
}
return await query
.sort(sort)
.skip(skip)
.limit(limit)