Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/pages-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ jobs:
test -f video-engineer.html
grep -qi '<title>' index.html
! grep -R -n -E '^(<<<<<<<|=======|>>>>>>>)' index.html script-writing-machine.html video-engineer.html

- uses: actions/setup-node@v4
with:
node-version: '22'

- name: Validate public registry and embedded fallbacks
run: npm run check-manifest
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ GitHub Pages org site for DaveHomeAssist. Contains the project hub (index.html),

## Manifest Sync

`project-manifest.json` is the single source of truth for the project list. Both `private-hub.html` and `index.html` embed a `FALLBACK_MANIFEST` block so the hubs still render if the fetch fails. After editing `project-manifest.json`, run `npm run sync-manifest` to propagate changes to both hubs' embedded fallbacks. Never edit the `FALLBACK_MANIFEST` blocks directly.
`project-manifest.json` is the single source of truth for the project list. `index.html`, `public-hub.html` and `private-hub.html` each embed a `FALLBACK_MANIFEST` block so the hubs still render if the fetch fails. After editing `project-manifest.json`, run `npm run sync-manifest` to propagate changes to every embedded fallback (the target list lives in `scripts/sync-manifest.mjs`; add any new hub page there). Never edit the `FALLBACK_MANIFEST` blocks directly. `node scripts/sync-manifest.mjs --check` reports drift without writing.

## Documentation Maintenance

Expand All @@ -36,7 +36,9 @@ GitHub Pages org site for DaveHomeAssist. Contains the project hub (index.html),
| 002 | P1 | resolved | Video engineer LinkedIn URL points to generic linkedin.com | Fixed: href updated to https://www.linkedin.com/in/daverobertson93/ |
| 003 | P2 | obsolete | Elysium nav links hidden on screens under 480px with no fallback | Elysium pages removed from repo; issue no longer applies |
| 004 | P2 | obsolete | Elysium back link uses inline onmouseover/onmouseout | Elysium pages removed from repo; issue no longer applies |
| 005 | P2 | open | Video engineer page has no back link to main portfolio | Users cannot navigate back to the hub |
| 005 | P2 | resolved | Video engineer page has no back link to main portfolio | Fixed 2026-07-11 in 93ca6c6: "Back to Portfolio" link added to video-engineer.html |
| 009 | P0 | resolved | private-hub.html threw ReferenceError on load (loadManifest removed in e21df48) | Fixed 2026-09-10: loader, validateManifest and manifestError restored |
| 010 | P1 | resolved | public-hub.html FALLBACK_MANIFEST was not covered by sync-manifest and drifted to the 2026-04-12 registry | Fixed 2026-09-10: added to sync targets, regenerated, drift check gates CI |
| 006 | P1 | resolved | Public manifest exposed 11 more private repo URLs plus localPath/runCommand values | Fixed 2026-07-03: blanked 10 private URLs, repointed BPMDelayCalc to public bpm-delay-calculator, cleared all localPath/runCommand |
| 007 | P2 | resolved | private-hub.html was indexable and listed in sitemap.xml | Fixed 2026-07-03: robots meta set to noindex,nofollow; removed from sitemap |
| 008 | P2 | resolved | index.html footer linked to missing archives/index-v1.html (live 404) | Fixed 2026-07-03: link removed |
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"type": "module",
"scripts": {
"validate-manifest": "node scripts/validate-public-manifest.mjs",
"sync-manifest": "npm run validate-manifest && node scripts/sync-manifest.mjs"
"check-manifest": "npm run validate-manifest && node scripts/sync-manifest.mjs --check",
"sync-manifest": "npm run validate-manifest && node scripts/sync-manifest.mjs",
"test": "npm run check-manifest"
}
}
70 changes: 68 additions & 2 deletions private-hub.html
Original file line number Diff line number Diff line change
Expand Up @@ -947,8 +947,8 @@ <h2 id="drawerTitle">Project details</h2>

<script>
// ── Manifest loader ──
// In production: fetch('project-manifest.json').then(...)
// For now, inline:
// project-manifest.json is fetched in loadManifest(); this embedded copy is the
// offline fallback and is regenerated by scripts/sync-manifest.mjs. Do not edit it.
const FALLBACK_MANIFEST = {
"meta": {
"owner": "DaveHomeAssist",
Expand Down Expand Up @@ -2207,6 +2207,72 @@ <h2 id="drawerTitle">Project details</h2>
}
};

let manifestData = FALLBACK_MANIFEST;

let manifestError = null;

function validateManifest(data) {
const errors = [];
const warnings = [];
if (!data || typeof data !== 'object') {
errors.push('Manifest is not an object');
return { ok: false, errors, warnings };
}
if (!data.meta || typeof data.meta !== 'object') {
errors.push('Missing meta object');
} else if (!data.meta.owner) {
errors.push('Missing meta.owner');
}
if (!Array.isArray(data.projects)) {
errors.push('projects is not an array');
} else {
data.projects.forEach((p, i) => {
if (!p || typeof p !== 'object') {
errors.push(`projects[${i}] is not an object`);
return;
}
if (!p.id) errors.push(`projects[${i}] missing id`);
if (!p.name) errors.push(`projects[${i}] missing name`);
if (!p.status) warnings.push(`projects[${i}] (${p.id || '?'}) missing status`);
if (!p.category) warnings.push(`projects[${i}] (${p.id || '?'}) missing category`);
if (!p.description) warnings.push(`projects[${i}] (${p.id || '?'}) missing description`);
if (!Array.isArray(p.tags)) warnings.push(`projects[${i}] (${p.id || '?'}) tags is not an array`);
});
}
if (!data.categories || typeof data.categories !== 'object') {
warnings.push('Missing categories map');
}
if (!data.hostingPlatforms || typeof data.hostingPlatforms !== 'object') {
warnings.push('Missing hostingPlatforms map');
}
return { ok: errors.length === 0, errors, warnings };
}

async function loadManifest() {
try {
const response = await fetch('./project-manifest.json', { cache: 'no-store' });
if (!response.ok) {
throw new Error(`Manifest request failed with ${response.status}`);
}
const data = await response.json();
const result = validateManifest(data);
if (!result.ok) {
console.error('Manifest validation failed:', result.errors);
manifestError = 'Manifest invalid, using fallback';
manifestData = FALLBACK_MANIFEST;
return;
}
if (result.warnings.length) {
console.warn('Manifest validation warnings:', result.warnings);
}
manifestData = data;
} catch (error) {
console.warn('Using embedded project manifest fallback.', error);
manifestError = 'Manifest failed to load, using fallback';
manifestData = FALLBACK_MANIFEST;
}
}

const STORAGE_KEY = 'private-hub-state';
const DEFAULT_STATE = {
search: '',
Expand Down
Loading
Loading