mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
feat(career): profile passport wall, home career card, shareable PNG card (#955)
* feat(career): profile passport wall, home career card, shareable PNG card Career v3, WS2. The identity artifact leaves the plugin tab: - Profile: #v3-profile-passports-mount (core, one div) filled by career on v3:profile-rendered — per-instrument shelves of earned covers, hours, gig count, open-career link. Absent-not-empty. - Home: the plugin-count stat tile becomes #v3-dash-career-slot with the old stat as fallback content; career replaces it with a trading-card tile (leather + foil shine, badge count, hours, closest-stamp ask) on the existing v3:dashboard-rendered event. - Shareable card: static/js/blob-io.js (downloadBlob lifts the idiom duplicated verbatim in settings-io/diagnostics-export — both refactored; copyImageBlob wraps ClipboardItem, returns false to signal the download fallback). Earned passports get Save/Copy card: a natively-drawn 480×640 canvas (leather, stamp ring, stubs+hours line); copy falls back to download with a notice when the clipboard refuses. - Mount-point convention documented in docs/plugin-v3-ui.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * 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> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
831117fb96
commit
6272af8d33
@@ -150,3 +150,30 @@ test('ppFillFraction: song progress toward the bar, in-progress only', () => {
|
||||
assert.equal(ppFillFraction(p('in_progress', 3, 0)), 0); // no bar → no fill
|
||||
assert.equal(ppFillFraction(null), 0);
|
||||
});
|
||||
|
||||
test('careerTotals / wall + dash card stay absent without commitment', () => {
|
||||
const w = load();
|
||||
const t = w.__careerPassportTest;
|
||||
// No _pp at all → null; committed-less view → null (absent-not-empty).
|
||||
assert.equal(t.careerTotals(), null);
|
||||
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: {
|
||||
guitar: { committed_at: 'x', passports: [
|
||||
{ badge: 'earned', seconds_total: 3600, genre: 'Blues', genre_key: 'blues' },
|
||||
{ badge: 'in_progress', seconds_total: 120, genre: 'Funk', genre_key: 'funk',
|
||||
qualifying_count: 4, requirement: { songs: 5, min_stars: 2 } }] },
|
||||
bass: { committed_at: null, passports: [] },
|
||||
} });
|
||||
const totals = t.careerTotals();
|
||||
assert.equal(totals.badges, 1);
|
||||
assert.equal(totals.seconds, 3720);
|
||||
assert.equal(totals.walls.length, 1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user