Workspaces & The Tower
Mallnline organizes its administrative interfaces into Workspaces โ role-specific dashboards named after physical spaces within the virtual mall. Each workspace serves a distinct persona and has its own subdomain in the production architecture.
Workspace Architecture
| Workspace | Subdomain | Persona | Frontend Route | Description |
|---|---|---|---|---|
| The Deck | deck.mallnline.com |
Malet Owners | /dashboard, /[malet]/manage/* |
Per-Malet management โ dashboard, analytics, payments, inventory, trash, edit history |
| The Studio | studio.mallnline.com |
Developers | /dev |
SDK docs, API references, webhook management |
| The Tower | tower.mallnline.com |
Platform Admins | /tower |
Platform-wide admin dashboard โ analytics, users, templates, search config, deprecation |
NOTE
In the current monolith frontend, workspaces are routes within the main SvelteKit app. In the production subdomain architecture, each workspace becomes its own entry point with shared authentication via uID.
The Tower (`/tower`)
The Tower is the platform-wide admin dashboard. It is restricted to internal Mallnline administrators (is_privileged users only). Non-privileged users are redirected to The Deck.
IMPORTANT
The Tower is designed to eventually live at tower.mallnline.com as its own standalone app. No customer-facing features should live in The Tower. Owner-specific features belong in The Deck.
Access Control
The Tower uses a client-side is_privileged gate:
Authenticated? โโโyesโโโ is_privileged? โโโyesโโโ Full Tower Dashboard
โ โ
no no โ Redirect to /dashboard
โ
โโโ Redirect to /auth?redirect=/tower
The legacy /admin route redirects to /tower for backward compatibility.
Navigation Entry Points
The Tower link is visible only to privileged users:
| Location | Component | Selector | Gated By |
|---|---|---|---|
| Side Navigation | SideNav.svelte |
[data-testid="sidenav-admin-link"] |
$currentUser?.is_privileged |
| User Menu | UserMenu.svelte |
[data-testid="user-menu-admin-link"] |
$currentUser?.is_privileged |
| Footer | SoleContent.svelte |
[data-testid="sole-admin-link"] |
$currentUser?.is_privileged |
| Settings | AdminSection.svelte |
[data-testid="settings-admin-link"] |
Always visible (link goes to /tower) |
Tab Architecture
The Tower uses a primary/secondary tab system with platform-only tabs:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Overview โ Users โ Analytics โ More โพ โ
โ โ
โ โโ More Dropdown โโโโโโโโโโโโโโโ โ
โ โ Templates โ Search Config โ Deprecation โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
State type:
let activeTab = $state<
'overview' | 'users' | 'analytics' |
'templates' | 'search' | 'deprecation'
>('overview');
NOTE
Deck-scoped tabs (Payments, Inventory, Trash, Edit History) were extracted to flat sub-routes in the ngwenya-deck standalone app in the Deck โ Tower Separation epic.
The Deck (`deck.mallnline.com`)
The Deck is the Malet Owner standalone workspace. It provides per-malet tools for managing products, analytics, and operations.
Deck Sub-Routes
| Route | Purpose | Key Component |
|---|---|---|
/dashboard |
Owner overview with quick actions | Standalone page |
/analytics |
Per-malet Blog & Media analytics | BlogAnalytics, MediaAnalytics |
/payments |
Murchase history for owned Malets | PaymentsHistory |
/inventory |
Stock levels for owned products | InventoryPanel |
/trash |
Soft-deleted items with restore | TrashBin |
/history |
Edit history with activity feed | ActivityFeedTimeline |
Deck Query Scoping
Deck sub-routes use scoped queries to ensure owners only see their own data:
// Uses maletId: { in: $maletIds } filter on @FilterableField
export const GET_OWNED_MALET_PRODUCTS = gql`
query GetOwnedMaletProducts($maletIds: [String!]!, $first: Int!) {
products(paging: { first: $first }, filter: { maletId: { in: $maletIds } }) {
edges { node { id name basePrice status totalInventory ... } }
}
}
`;
Analytics Sub-Tabs (Tower โ Platform-Wide)
The Tower's Analytics tab contains five sub-views showing platform-wide data:
| Sub-Tab | Component | Data Source | Key Visualizations |
|---|---|---|---|
| Revenue | RevenueAnalytics.svelte |
GET_MY_OWNED_MALETS, orders queries |
KPI cards, revenue/volume charts, entity breakdown, top Malets leaderboard |
| Blog | BlogAnalytics.svelte |
Blog posts per Malet | Engagement KPIs, post volume chart, status donut, top authors/posts |
| Media | MediaAnalytics.svelte |
Media subgraph | Upload volume, storage growth, MIME distribution, processing health, top Malets by storage |
| Search | SearchAnalytics.svelte |
Faceted search, reconciliation, synonyms | Index composition donut, categories, Malet leaderboard, tag cloud, synonym coverage, reconciliation health |
| Alerts | AlertsAnalytics.svelte |
alertLogs, dlqSummary (alerts subgraph) |
Delivery health donut, channel distribution bars, event type breakdown, recent delivery feed, DLQ health panel |
Analytics Sub-Tabs (Deck โ Per-Malet)
The Deck's /analytics route shows per-malet analytics (Blog + Media only):
let activeSubTab = $state<'blog' | 'media'>('blog');
Platform-wide analytics (Revenue, Search, Alerts) remain exclusively in The Tower.
Search Analytics Data Sources
The Search Analytics dashboard is unique in that it composes from three separate query patterns rather than a single subgraph analytics resolver:
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SearchAnalytics โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 1. Zero-query Facet โโโโ SEARCH_ITEMS (q="", limit=0)
โ Search โ โ type, category, malet, section facets
โ โ
โ 2. Reconciliation โโโโ GET_RECONCILIATION_STATUS
โ Status โ โ lastRun, added, removed, unchanged, errors
โ โ
โ 3. Synonym Coverage โโโโ GET_SEARCH_SYNONYMS
โ โ โ groups per vertical
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
This pattern avoids adding dedicated analytics resolvers to the search subgraph โ it reuses existing operational queries and performs client-side aggregation.
Organization Analytics
Separate from The Tower, the Organization Management page (/orgs/[slug]/manage) has its own Analytics tab implemented by OrgAnalyticsPanel.svelte. This tab provides org-scoped metrics:
- KPI Cards: Total Members, Total Activity, Active Contributors, Teams
- Activity Over Time: 30-day bar chart from
organizationAuditEvents - Role Distribution: Donut chart of
OWNER/ADMIN/MEMBERratios - Event Type Breakdown: Horizontal bars for
MEMBER_ADDED,ROLE_CHANGED, etc. - Top Contributors: Leaderboard with medal emojis
- Teams Table: Sub-group listing from
organizationTeams
Like Search Analytics, this uses client-side aggregation from existing queries rather than dedicated analytics resolvers.
Utility Functions
Both analytics components share common formatting utilities:
function formatNumber(n: number): string {
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
return n.toString();
}
function formatDuration(ms: number): string {
if (ms < 1000) return `${ms}ms`;
return `${(ms / 1000).toFixed(1)}s`;
}
function timeAgo(dateStr: string): string {
const diff = Date.now() - new Date(dateStr).getTime();
const mins = Math.floor(diff / 60000);
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
return `${Math.floor(hrs / 24)}d ago`;
}
These are tested in tests/searchAnalytics.test.ts (15 tests), tests/orgAnalytics.test.ts (16 tests), and tests/alertsAnalytics.test.ts (22 tests).
Alerts Analytics Data Sources
The Alerts Analytics dashboard consumes from the alerts subgraph's AlertLog module via GraphQL:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AlertsAnalytics โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 1. dlqSummary โโโโ Aggregated status counts
โ โ (PENDING/DELIVERED/FAILED/DEAD + total)
โ โ
โ 2. alertLogs(filter) โโโโ Paginated delivery logs
โ โ โ channel, status, eventType, recipient
โ โ โ client-side aggregation for charts
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Alerts-specific utilities in src/lib/queries/alerts.ts:
// Aggregate logs by any field (channel, status, eventType)
function aggregateByField<T>(logs: AlertLog[], field: string): { key: T; count: number }[]
// Mask recipients for privacy display
function truncateRecipient(r: string): string // j***@e***.com or ***4567
// Calculate percentage with 1 decimal
function percentOf(part: number, total: number): string
Display label maps provide human-readable names, colors, and icons:
CHANNEL_LABELS/CHANNEL_COLORS/CHANNEL_ICONSโ EMAIL, SMS, PUSH, IN_APPSTATUS_LABELS/STATUS_COLORSโ PENDING, DELIVERED, FAILED, DEADEVENT_TYPE_LABELSโ 12 event types (notify_email, order_status_changed, org_invite_created, etc.)
File Reference
| File | Purpose |
|---|---|
src/routes/tower/+page.svelte |
Tower page โ tabs, data fetching, layout |
src/routes/admin/+page.svelte |
Legacy redirect โ /tower |
ngwenya-deck/src/routes/dashboard/+page.svelte |
Deck dashboard with quick actions |
ngwenya-deck/src/routes/analytics/+page.svelte |
Per-malet Blog & Media analytics |
ngwenya-deck/src/routes/payments/+page.svelte |
Murchase history for owned Malets |
ngwenya-deck/src/routes/inventory/+page.svelte |
Inventory health for owned products |
ngwenya-deck/src/routes/trash/+page.svelte |
Soft-deleted items with restore |
ngwenya-deck/src/routes/history/+page.svelte |
Edit history with activity feed |
src/lib/components/admin/RevenueAnalytics.svelte |
Revenue sub-tab (Tower) |
src/lib/components/admin/BlogAnalytics.svelte |
Blog sub-tab (Tower + Deck) |
src/lib/components/admin/MediaAnalytics.svelte |
Media sub-tab (Tower + Deck) |
src/lib/components/admin/SearchAnalytics.svelte |
Search sub-tab (Tower) |
src/lib/components/admin/AlertsAnalytics.svelte |
Alerts sub-tab (Tower) |
src/lib/queries/admin.ts |
GET_OWNED_MALET_PRODUCTS scoped query |
src/lib/queries/alerts.ts |
Alerts queries, types, display helpers |
src/lib/components/admin/OrgAnalyticsPanel.svelte |
Org manage analytics tab |
src/routes/lobby/SideNav.svelte |
Owner Tools & Platform sections |
src/lib/components/UserMenu.svelte |
Deck + Tower buttons in user dropdown |
src/routes/lobby/SoleContent.svelte |
Footer Deck + Tower links |
src/lib/utils/adminUtils.ts |
isAdmin() helper |
tests/searchAnalytics.test.ts |
Search analytics utility tests |
tests/orgAnalytics.test.ts |
Org analytics utility tests |
tests/alertsAnalytics.test.ts |
Alerts analytics utility tests (22 tests) |
Related
- Standalone Tower Dashboard โ Architecture of the standalone extraction and decoupled theme system.
- Tower Subgraph โ Rust/Axum subgraph powering The Tower's error tracking data via federated GraphQL
- Tower Health Observability โ Dual-layer probing architecture powering the Platform Health widget on the Overview tab
- Alerts & Resilience โ Backend alerts subgraph architecture, DLQ, delivery channels
- Search Engine Administration โ Backend search operations, reconciliation, and synonym management that power Search Analytics
- Organization Management โ Organization manage page and the Analytics tab
- Admin Dashboard & Audit Tools โ User-facing support manual for The Tower
- Owner Destinations โ The Deck, Register & Pricing โ Dedicated Malet Owner routes (
/owner/register,/dashboard,/owner/pricing) and tab-by-tab Deck โ Tower audit - Platform Admin Provisioning โ Dedicated
platform_adminstable, invite-chain access control, and 3-tier role system - Shared Query Package โ
@ngwenya/queriesworkspace package that eliminates query duplication across Deck, Tower, and Storefront