Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/agent/src/api/views-registry.kinds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ function fixturePlugin(): Plugin {
{ id: "vk-system", label: "Sys", viewKind: "system", bundleUrl: "x" },
{ id: "vk-release", label: "Rel", viewKind: "release", bundleUrl: "x" },
{ id: "vk-dev", label: "Dev", viewKind: "developer", bundleUrl: "x" },
{ id: "vk-preview", label: "Prev", viewKind: "preview", bundleUrl: "x" },
{
id: "vk-preview",
label: "Prev",
viewKind: "preview",
group: "wallet",
bundleUrl: "x",
},
// legacy gate still maps to developer
{ id: "vk-legacy", label: "Legacy", developerOnly: true, bundleUrl: "x" },
],
Expand Down Expand Up @@ -106,4 +112,12 @@ describe("listViews kind filtering", () => {
"vk-system",
]);
});

it("preserves app-shell grouping metadata for client launcher curation", async () => {
await register();
const grouped = listViews({ includeAllKinds: true }).find(
(view) => view.id === "vk-preview",
);
expect(grouped?.group).toBe("wallet");
});
});
2 changes: 2 additions & 0 deletions packages/core/src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,8 @@ export interface ViewDeclaration {
path?: string;
/** Sort priority in the view manager — lower appears first. Default 100. */
order?: number;
/** Optional named group shared with app-shell page registrations. */
group?: string;
/** Tags for search and discovery (e.g. ["finance", "crypto"]). */
tags?: string[];
/**
Expand Down
6 changes: 6 additions & 0 deletions packages/ui/src/components/pages/WalletSectionNav.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* Wallet section navigation renders sub-tabs from app-shell pages that declare
* the wallet group. The wallet inventory page owns the root `/wallet` tab while
* plugin pages join or leave the section through their own registration data.
*/

import { useSyncExternalStore } from "react";
import {
type AppShellPageRegistration,
Expand Down
22 changes: 17 additions & 5 deletions packages/ui/src/components/pages/launcher-curation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,26 @@ describe("curateLauncherPages", () => {
expect(ids(page)).toEqual(["chat", "wallet"]);
});

it("keeps hyperliquid/polymarket out of the launcher (wallet sub-views)", () => {
it("keeps wallet-group sub-pages out of the launcher", () => {
const page = curateLauncherPages(
[entry("wallet"), entry("hyperliquid"), entry("polymarket")],
[
entry("wallet"),
entry("perps", { group: "wallet" }),
entry("predictions", { group: "wallet" }),
],
{ isAosp: false, enabledKinds: ENABLED, cloudActive: true },
);
expect(ids(page)).toEqual(["wallet"]);
});

it("shows the same pages as ordinary apps when they do not declare a group", () => {
const page = curateLauncherPages(
[entry("wallet"), entry("perps"), entry("predictions")],
{ isAosp: false, enabledKinds: ENABLED, cloudActive: true },
);
expect(ids(page)).toEqual(["wallet", "perps", "predictions"]);
});

it("gates native-OS tiles to the AOSP fork", () => {
const views = [
entry("wallet"),
Expand Down Expand Up @@ -284,12 +296,12 @@ describe("curateLauncherPages — full realistic view set", () => {
entry("shopify"),
entry("facewear", { viewKind: "preview" }),
entry("smartglasses", { viewKind: "preview" }),
// Wallet + duplicate registrations + sub-views.
// Wallet + duplicate registrations + grouped sub-views.
entry("wallet", { viewKind: "system" }),
entry("inventory", { builtin: true, viewKind: "system" }),
entry("wallet.inventory"),
entry("hyperliquid"),
entry("polymarket"),
entry("perps", { group: "wallet" }),
entry("predictions", { group: "wallet" }),
// Automations + duplicates folded to one.
entry("automations", { viewKind: "system" }),
entry("triggers", { builtin: true }),
Expand Down
23 changes: 14 additions & 9 deletions packages/ui/src/components/pages/launcher-curation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
* declared, so the whole set hides together in production.
*
* Curation is a blocklist + canonical dedup, not a fixed allow-list: known apps
* are ordered, removed apps are hidden, duplicate registrations collapse to one
* tile, and everything else that is genuinely loaded and visible still appears
* so installing a new plugin app keeps working. Native-OS tiles (phone/messages/
* contacts/camera/files) only appear on the AOSP ElizaOS fork.
* are ordered, removed apps are hidden, grouped sub-pages collapse under their
* parent tile, duplicate registrations collapse to one tile, and everything else
* that is genuinely loaded and visible still appears so installing a new plugin
* app keeps working. Native-OS tiles (phone/messages/contacts/camera/files) only
* appear on the AOSP ElizaOS fork.
*/

import {
Expand Down Expand Up @@ -89,8 +90,7 @@ export const LAUNCHER_AOSP_ONLY_IDS: readonly string[] =
* Views that never appear in the launcher grid:
* - shell surfaces reached another way (views/apps launchers; background +
* voice are set from Settings/chat; character-select is inline),
* - removed apps (companion, model tester, shopify, wearables),
* - wallet sub-views (hyperliquid/polymarket open from inside the Wallet app).
* - removed apps (companion, model tester, shopify, wearables).
*/
export const LAUNCHER_HIDDEN_IDS: ReadonlySet<string> = new Set([
"views",
Expand All @@ -106,9 +106,6 @@ export const LAUNCHER_HIDDEN_IDS: ReadonlySet<string> = new Set([
"shopify",
"facewear",
"smartglasses",
// Wallet sub-views — reached from inside the Wallet app, not the launcher.
"hyperliquid",
"polymarket",
]);

/**
Expand Down Expand Up @@ -170,6 +167,13 @@ function launcherViewKind(canonicalId: string, entry: ViewEntry) {
return resolveViewKind(entry);
}

function isGroupedLauncherSubPage(
canonicalId: string,
entry: ViewEntry,
): boolean {
return entry.group === "wallet" && canonicalId !== "wallet";
}

/**
* Score competing registrations for the same canonical id so the richest one
* wins the single tile (a loaded standalone view beats an app-shell alias beats
Expand Down Expand Up @@ -238,6 +242,7 @@ export function curateLauncherPages(
for (const entry of entries) {
const canonicalId = canonicalLauncherId(entry.id);
if (LAUNCHER_HIDDEN_IDS.has(canonicalId)) continue;
if (isGroupedLauncherSubPage(canonicalId, entry)) continue;
// Cloud-only tiles (e.g. the Cloud Applications dashboard) never surface
// unless the user is signed into Eliza Cloud.
if (LAUNCHER_CLOUD_IDS.has(canonicalId) && !cloudActive) continue;
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/hooks/useAvailableViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export interface ViewRegistryEntry {
tags?: string[];
/** Sort priority for launcher/nav surfaces (lower = earlier). */
order?: number;
/** Optional named group shared with app-shell page registrations. */
group?: string;
/**
* When true, the view only appears when Developer Mode is enabled.
* Equivalent to `viewKind: "developer"`.
Expand Down Expand Up @@ -317,6 +319,7 @@ function appShellPageToViewEntry(
developerOnly: page.developerOnly,
viewKind: page.viewKind,
order: page.order,
group: page.group,
backgroundPolicy: page.backgroundPolicy,
visibleInManager: true,
builtin: false,
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/hooks/view-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export interface ViewEntry {
viewKind?: ViewKind;
/** Sort priority for launcher/nav surfaces (lower = earlier). */
order?: number;
/** Optional named group shared with app-shell page registrations. */
group?: string;
/** Source records (one is set depending on `kind`). */
view?: ViewRegistryEntry;
app?: RegistryAppInfo;
Expand Down Expand Up @@ -125,6 +127,7 @@ export function viewToEntry(view: ViewRegistryEntry): ViewEntry {
developerOnly: view.developerOnly,
viewKind: view.viewKind,
order: view.order,
group: view.group,
view,
};
}
Expand Down
1 change: 1 addition & 0 deletions plugins/plugin-hyperliquid/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export const hyperliquidPlugin: Plugin = {
"Hyperliquid perpetual markets — positions, trading status, and market data",
icon: "TrendingUp",
path: "/hyperliquid",
group: "wallet",
modalities: ["gui", "xr", "tui"],
bundlePath: "dist/views/bundle.js",
componentExport: "HyperliquidView",
Expand Down
1 change: 1 addition & 0 deletions plugins/plugin-polymarket/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const polymarketPlugin: Plugin = {
"Polymarket prediction markets — market discovery, orderbook, and positions",
icon: "BarChart2",
path: "/polymarket",
group: "wallet",
modalities: ["gui", "xr", "tui"],
bundlePath: "dist/views/bundle.js",
componentExport: "PolymarketView",
Expand Down
Loading