fix(career): external surfaces stay absent until a passport exists

CodeRabbit on #955: a bare commitment produced a zero-passport wall and
replaced the dashboard fallback. Docs also now say mounts may hold
fallback content and plugins REPLACE, never append.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
byrongamatos
2026-07-14 01:25:23 +02:00
co-authored by Claude Fable 5
parent e4cb15d9c7
commit 60004c10d0
3 changed files with 15 additions and 6 deletions
+6 -4
View File
@@ -192,10 +192,12 @@ out of the capability graph.
## Injecting into core shells (profile, dashboard)
Core screens that accept plugin sections render **empty mount points** and
announce each (re)build with a DOM event, because their `innerHTML` swap wipes
anything previously injected. A plugin listens for the event and fills the
mount by id — the same seam every time:
Core screens that accept plugin sections render **mount points** — usually
empty, sometimes holding core's own **fallback content** (the Dashboard's
career slot ships the plugin-count stat) — and announce each (re)build with a
DOM event, because their `innerHTML` swap wipes anything previously injected.
A plugin listens for the event and **replaces the mount's content** (never
append — a fallback may be present) by id — the same seam every time:
| Shell | Event | Mounts |
| --- | --- | --- |
+5 -2
View File
@@ -937,12 +937,15 @@
const walls = [];
for (const inst of (_pp.config || {}).instruments || []) {
const d = (_pp.instruments || {})[inst];
if (!d || !d.committed_at) continue;
// A commitment with no opened passport isn't a wall yet — the
// external surfaces (profile, home card) stay ABSENT until a
// passport exists (absent-not-empty).
if (!d || !d.committed_at || !(d.passports || []).length) continue;
const earned = (d.passports || []).filter((p) => p.badge === 'earned' || p.badge === 'gold');
badges += earned.length;
seconds += (d.passports || []).reduce((t, p) => t + (p.seconds_total || 0), 0);
gigs += d.gig_count || 0;
walls.push({ inst, earned, opened: (d.passports || []).length });
walls.push({ inst, earned, opened: d.passports.length });
}
if (!walls.length) return null;
return { badges, seconds, gigs, walls };
+4
View File
@@ -159,6 +159,10 @@ test('careerTotals / wall + dash card stay absent without commitment', () => {
t.setView({ config: { instruments: ['guitar'] },
instruments: { guitar: { committed_at: null, passports: [] } } });
assert.equal(t.careerTotals(), null);
// Committed but zero passports opened: still absent (no zero-wall).
t.setView({ config: { instruments: ['guitar'] },
instruments: { guitar: { committed_at: 'x', passports: [] } } });
assert.equal(t.careerTotals(), null);
// Committed with an earned badge + hours → totals aggregate.
t.setView({ config: { instruments: ['guitar', 'bass'] },
instruments: {