Initial commit: Converge portfolio rationalization app

Next.js app for mapping a customer's competitive tool landscape onto our
portfolio and building the consolidation business case: assessment
workbench, customer report and Word export, product catalog, competitive
knowledge base, dossier import, shareable landscape questionnaires with a
review inbox, tool-footprint and business-unit value analysis, and an
implementation timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 11:48:42 -04:00
commit d5c77543e8
64 changed files with 30861 additions and 0 deletions

27
scripts/test-client.mjs Normal file
View File

@@ -0,0 +1,27 @@
// Exercises the exact regression: an empty 200 body must throw, not resolve to undefined.
const { getJson, HttpError } = await import('/tmp/prt-test/client.mjs');
let pass = 0, fail = 0;
const check = (name, ok) => { ok ? (pass++, console.log(' PASS', name)) : (fail++, console.log(' FAIL', name)); };
// 1. Real endpoint still works.
const cat = await getJson('http://127.0.0.1:3500/api/catalog');
check('catalog loads', cat.products.length === 179);
// 2. Empty 200 body -> throws (previously: JSON.parse crash that killed Promise.all).
globalThis.fetch = async () => new Response('', { status: 200 });
try { await getJson('/empty'); check('empty body throws', false); }
catch (e) { check('empty body throws HttpError', e instanceof HttpError && /Empty response/.test(e.message)); }
// 3. 404 surfaces its status so the caller can show "not found" instead of an error.
globalThis.fetch = async () => new Response(JSON.stringify({error:'Not found'}), { status: 404 });
try { await getJson('/missing'); check('404 throws', false); }
catch (e) { check('404 carries status', e.status === 404 && e.message === 'Not found'); }
// 4. A transient failure is retried once and then succeeds.
let n = 0;
globalThis.fetch = async () => (++n === 1 ? new Response('', { status: 200 }) : new Response(JSON.stringify({ok:true}), { status: 200 }));
check('retries once then succeeds', (await getJson('/flaky')).ok === true && n === 2);
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail ? 1 : 0);