diff --git a/client/src/components/shared/EmptyState.tsx b/client/src/components/shared/EmptyState.tsx index 35b838e90..d1c54731c 100644 --- a/client/src/components/shared/EmptyState.tsx +++ b/client/src/components/shared/EmptyState.tsx @@ -10,6 +10,10 @@ import MDancingTrek, { type TrekScene, type TrekMood } from '../../mobile/compon * so we map them onto the desktop palette here. Its `.trek-*` choreography is * global CSS, so it animates outside the shell as-is. `surface` should match the * background the state sits on so the cut-out eyes read as holes (default: card). + * + * `layout="row"` puts the mascot beside the title with tight padding, for short + * content-sized panels (the Atlas glass pill) where the stacked look towers over + * the sibling states. */ export default function EmptyState({ scene = 'idle', @@ -17,6 +21,7 @@ export default function EmptyState({ title, size = 104, surface = 'var(--bg-card)', + layout = 'stack', className = '', action, }: { @@ -25,13 +30,17 @@ export default function EmptyState({ title: string size?: number surface?: string + layout?: 'stack' | 'row' className?: string /** Optional call to action under the title, for states that have an obvious next step. */ action?: ReactNode }) { + const layoutClasses = layout === 'row' + ? 'flex flex-row items-center justify-center gap-3 px-6 py-3' + : 'flex flex-col items-center justify-center gap-3 px-6 py-12 text-center' return (
diff --git a/client/src/pages/AtlasPage.tsx b/client/src/pages/AtlasPage.tsx index 204bcd387..ca123e61f 100644 --- a/client/src/pages/AtlasPage.tsx +++ b/client/src/pages/AtlasPage.tsx @@ -525,7 +525,7 @@ function SidebarContent({ data, stats, countries, selectedCountry, countryDetail return ( <> {tabBar} - + ) } diff --git a/roadmap.md b/roadmap.md index e7eec545c..bf13b9830 100644 --- a/roadmap.md +++ b/roadmap.md @@ -181,9 +181,10 @@ Ordered by value: 'write' }` appears ~100+ times across `.mcp.ts` files as free strings. A typo'd group never matches a real scope: scoped tokens get denied (fail closed) but `scopes: null` sessions pass (fail open) — silent scope-model drift either way. Mechanism: derive a - `ScopeGroup` union from `SCOPES` and type the decorator's `access.group` against it via - the same `declare module '@trek/nest-mcp'` augmentation already used for `McpContext` - (keeps nest-mcp extraction-clean); plus a **boot gate in the MCP registry**: at discovery + `ScopeGroup` union from `SCOPES` and type the decorator's `access.group` against it + (originally a `declare module '@trek/nest-mcp'` augmentation; since the 2026-08-22 fold + the registries live directly in `server/src/nest-mcp/types.ts`); plus a **boot gate in + the MCP registry**: at discovery time, refuse to boot on an `access.group`/tool-name/`when` combination that doesn't resolve against `SCOPES` — the body-contract ratchet pattern applied to the MCP surface. The registry the user-facing scopes UI reads (`SCOPE_INFO`) then serves decorators, the