mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
refactor(shared-branding): derive APP_URLS from APP_ICONS
The hand-maintained APP_URLS map kept silently drifting from the
AppIconId union — most recently the new 'who' entry was missing,
which crashed getPillAppItems at runtime with "Cannot read properties
of undefined (reading 'prod')". Drift was already flagged by the type
system but the error was lost in the existing svelte-check noise.
APP_URLS is now generated at module load by walking Object.keys of
APP_ICONS (the source of AppIconId), so every id is guaranteed a URL.
A small APP_URL_OVERRIDES map carries the handful of apps that don't
follow the unified mana.how/{id} pattern (root path for the unified
shell, subdomains for standalone apps like arcade).
Adds two integrity tests as defense-in-depth: one asserts every
MANA_APPS id has a matching APP_ICONS icon, the other asserts every
AppIconId resolves to a non-empty dev+prod URL. Both would have caught
the 'who' regression on its own without needing svelte-check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c7fd9369c9
commit
e00e6f5a08
2 changed files with 49 additions and 45 deletions
|
|
@ -7,7 +7,9 @@ import {
|
|||
getManaApp,
|
||||
getManaAppsByStatus,
|
||||
MANA_APPS,
|
||||
APP_URLS,
|
||||
} from './mana-apps';
|
||||
import { APP_ICONS } from './app-icons';
|
||||
|
||||
describe('getTierLevel', () => {
|
||||
it('returns correct levels for all tiers', () => {
|
||||
|
|
@ -121,4 +123,27 @@ describe('MANA_APPS integrity', () => {
|
|||
expect(app.description.en).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('every MANA_APPS entry has a corresponding APP_ICONS icon', () => {
|
||||
// Catches the case where a new app is registered but no icon was
|
||||
// added to APP_ICONS (which is also where AppIconId is derived).
|
||||
const missing = MANA_APPS.filter((app) => !APP_ICONS[app.id]);
|
||||
expect(missing.map((a) => a.id)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('APP_URLS integrity', () => {
|
||||
it('every AppIconId resolves to a non-empty dev + prod URL', () => {
|
||||
// Regression guard: when `who` was added to AppIconId but never to
|
||||
// the hand-maintained APP_URLS map, getPillAppItems crashed at
|
||||
// runtime with "Cannot read properties of undefined (reading
|
||||
// 'prod')". APP_URLS is now derived from APP_ICONS, so this test
|
||||
// will fail loudly if that derivation ever stops covering every id.
|
||||
for (const id of Object.keys(APP_ICONS) as Array<keyof typeof APP_ICONS>) {
|
||||
const entry = APP_URLS[id];
|
||||
expect(entry, `APP_URLS missing entry for "${id}"`).toBeDefined();
|
||||
expect(entry.dev, `APP_URLS["${id}"].dev is empty`).toBeTruthy();
|
||||
expect(entry.prod, `APP_URLS["${id}"].prod is empty`).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -795,57 +795,36 @@ export const APP_SLIDER_LABELS = {
|
|||
} as const;
|
||||
|
||||
/**
|
||||
* Default app URLs for local development and production
|
||||
*/
|
||||
/**
|
||||
* App URLs — unified app uses internal paths, separate apps use subdomains.
|
||||
* App URLs — derived automatically from MANA_APPS.
|
||||
*
|
||||
* All productivity apps are now served under mana.how/{appId}.
|
||||
* Games remain on separate subdomains.
|
||||
* Almost every productivity app lives under the unified mana.how/{id}
|
||||
* surface, so listing each entry by hand was duplicated bookkeeping that
|
||||
* silently drifted (a missing entry crashed `getPillAppItems` at runtime
|
||||
* when the new `who` app was added). Instead we generate the map from
|
||||
* MANA_APPS at module load and only override the few apps that don't
|
||||
* follow the unified-path convention.
|
||||
*
|
||||
* To add a new app: register it in MANA_APPS and you're done. To put it
|
||||
* on its own subdomain or use a custom port: add an entry to
|
||||
* APP_URL_OVERRIDES below.
|
||||
*/
|
||||
export const APP_URLS: Record<AppIconId, { dev: string; prod: string }> = {
|
||||
// ─── Unified App (internal paths) ─────────────────────────
|
||||
const APP_URL_OVERRIDES: Partial<Record<AppIconId, { dev: string; prod: string }>> = {
|
||||
// The unified app itself lives at the root, not at /mana.
|
||||
mana: { dev: 'http://localhost:5173', prod: 'https://mana.how' },
|
||||
todo: { dev: 'http://localhost:5173/todo', prod: 'https://mana.how/todo' },
|
||||
calendar: { dev: 'http://localhost:5173/calendar', prod: 'https://mana.how/calendar' },
|
||||
contacts: { dev: 'http://localhost:5173/contacts', prod: 'https://mana.how/contacts' },
|
||||
chat: { dev: 'http://localhost:5173/chat', prod: 'https://mana.how/chat' },
|
||||
picture: { dev: 'http://localhost:5173/picture', prod: 'https://mana.how/picture' },
|
||||
cards: { dev: 'http://localhost:5173/cards', prod: 'https://mana.how/cards' },
|
||||
zitare: { dev: 'http://localhost:5173/zitare', prod: 'https://mana.how/zitare' },
|
||||
clock: { dev: 'http://localhost:5173/clock', prod: 'https://mana.how/clock' },
|
||||
music: { dev: 'http://localhost:5173/music', prod: 'https://mana.how/music' },
|
||||
storage: { dev: 'http://localhost:5173/storage', prod: 'https://mana.how/storage' },
|
||||
presi: { dev: 'http://localhost:5173/presi', prod: 'https://mana.how/presi' },
|
||||
inventory: { dev: 'http://localhost:5173/inventory', prod: 'https://mana.how/inventory' },
|
||||
photos: { dev: 'http://localhost:5173/photos', prod: 'https://mana.how/photos' },
|
||||
skilltree: { dev: 'http://localhost:5173/skilltree', prod: 'https://mana.how/skilltree' },
|
||||
citycorners: { dev: 'http://localhost:5173/citycorners', prod: 'https://mana.how/citycorners' },
|
||||
times: { dev: 'http://localhost:5173/times', prod: 'https://mana.how/times' },
|
||||
context: { dev: 'http://localhost:5173/context', prod: 'https://mana.how/context' },
|
||||
questions: { dev: 'http://localhost:5173/questions', prod: 'https://mana.how/questions' },
|
||||
nutriphi: { dev: 'http://localhost:5173/nutriphi', prod: 'https://mana.how/nutriphi' },
|
||||
planta: { dev: 'http://localhost:5173/planta', prod: 'https://mana.how/planta' },
|
||||
uload: { dev: 'http://localhost:5173/uload', prod: 'https://mana.how/uload' },
|
||||
calc: { dev: 'http://localhost:5173/calc', prod: 'https://mana.how/calc' },
|
||||
moodlit: { dev: 'http://localhost:5173/moodlit', prod: 'https://mana.how/moodlit' },
|
||||
memoro: { dev: 'http://localhost:5173/memoro', prod: 'https://mana.how/memoro' },
|
||||
guides: { dev: 'http://localhost:5173/guides', prod: 'https://mana.how/guides' },
|
||||
habits: { dev: 'http://localhost:5173/habits', prod: 'https://mana.how/habits' },
|
||||
notes: { dev: 'http://localhost:5173/notes', prod: 'https://mana.how/notes' },
|
||||
dreams: { dev: 'http://localhost:5173/dreams', prod: 'https://mana.how/dreams' },
|
||||
cycles: { dev: 'http://localhost:5173/cycles', prod: 'https://mana.how/cycles' },
|
||||
events: { dev: 'http://localhost:5173/events', prod: 'https://mana.how/events' },
|
||||
finance: { dev: 'http://localhost:5173/finance', prod: 'https://mana.how/finance' },
|
||||
places: { dev: 'http://localhost:5173/places', prod: 'https://mana.how/places' },
|
||||
wisekeep: { dev: 'http://localhost:5173/wisekeep', prod: 'https://mana.how/wisekeep' },
|
||||
news: { dev: 'http://localhost:5173/news', prod: 'https://mana.how/news' },
|
||||
mail: { dev: 'http://localhost:5173/mail', prod: 'https://mana.how/mail' },
|
||||
who: { dev: 'http://localhost:5173/who', prod: 'https://mana.how/who' },
|
||||
// ─── Separate Apps (own subdomains) ───────────────────────
|
||||
// Standalone apps on their own subdomain / port.
|
||||
arcade: { dev: 'http://localhost:5201', prod: 'https://arcade.mana.how' },
|
||||
};
|
||||
|
||||
export const APP_URLS: Record<AppIconId, { dev: string; prod: string }> = Object.fromEntries(
|
||||
(Object.keys(APP_ICONS) as AppIconId[]).map((id) => [
|
||||
id,
|
||||
APP_URL_OVERRIDES[id] ?? {
|
||||
dev: `http://localhost:5173/${id}`,
|
||||
prod: `https://mana.how/${id}`,
|
||||
},
|
||||
])
|
||||
) as Record<AppIconId, { dev: string; prod: string }>;
|
||||
|
||||
/**
|
||||
* App item type for PillNavigation app switcher
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue