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