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
11 changes: 11 additions & 0 deletions .changeset/30311-served-app-labels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@object-ui/app-shell': patch
---

fix(app-shell): every Console surface that names an app draws the served app label as served

Clause-②: no
Metadata-protocol: none

- **Now.** The app switcher, the sidebar's app title, the top bar's hidden-app menu, Home's app cards, apps strip, recent rows and setup link, the no-access empty state, the AI chat's app names, the marketplace's "Open app" buttons and the report index's other-app links draw the app `label` (and the app card its `description`) the `/meta/app` read served, as given. The server has already translated it, by its label order for an overridden app: a `.custom` pair's own text is kept over the base package's translation. Before, the served label reached the client's `apps.<name>.label` catalog lookup only as its fallback, so the base package's translation of the old name won.
- **Unchanged.** A pending draft under `?preview=draft`, a keyed label and an app with no label or description are still named by the catalog key, then by the machine name as before. An unedited app shows the same text as before, because the server serves the catalog's translation for it.
8 changes: 4 additions & 4 deletions ui/packages/app-shell/src/console/AppNoAccessEmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ import { Lock, PanelLeft } from 'lucide-react';
// already reaches it through `PreviewDraftEmptyState`, so the eager graph
// gains no new module.
import { Button, DataEmptyState } from '@object-ui/components';
import { useObjectLabel, useObjectTranslation } from '@object-ui/i18n';
import { resolveKeyedI18nLabel } from '../utils/index.js';
import { useObjectTranslation } from '@object-ui/i18n';
import { useServedAppLabel } from '../hooks/useServedAppLabel.js';

export interface AppNoAccessEmptyStateProps {
/** The active app, as the console's metadata list carries it. */
Expand All @@ -65,9 +65,9 @@ export interface AppNoAccessEmptyStateProps {

export function AppNoAccessEmptyState({ app, setupPath, reason = 'no-navigation' }: AppNoAccessEmptyStateProps) {
const { t } = useObjectTranslation();
const { appLabel } = useObjectLabel();
const { appLabel } = useServedAppLabel();
// The same label the header's app switcher shows for this app.
const label = appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) });
const label = appLabel(app);

if (reason === 'menu-only') {
return (
Expand Down
13 changes: 7 additions & 6 deletions ui/packages/app-shell/src/console/ai/AiChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useAuth } from '@object-ui/auth';
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
import { useObjectTranslation } from '@object-ui/i18n';
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
import { toast } from 'sonner';
import { Package as PackageIcon, Sparkles as SparklesIcon } from 'lucide-react';
import { useAdapter } from '../../providers/AdapterProvider.js';
Expand Down Expand Up @@ -2181,7 +2182,7 @@ export function ChatPane({
// Narrow the loosely-typed provider apps once (see MetadataAppItem) so the
// derivations below read fields without per-access casts.
const metadataApps = apps as MetadataAppItem[] | undefined;
const { appLabel } = useObjectLabel();
const { appLabel } = useServedAppLabel();
// Fold-insensitive, measured: this walks every message irrespective of role
// and reads `draftReview`/`builderHandoff`, which the adapter spreads through.
const boundPackageId = useMemo(
Expand All @@ -2202,7 +2203,7 @@ export function ChatPane({
const app = (metadataApps ?? []).find(
(a) => a._packageId === boundPackageId,
);
return app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : boundPackageId;
return app ? appLabel(app) : boundPackageId;
}, [boundPackageId, metadataApps, appLabel, t]);

// ADR-0057 A1.b edit mode — the resolved name of the app being EDITED
Expand All @@ -2214,7 +2215,7 @@ export function ChatPane({
const app = (metadataApps ?? []).find(
(a) => a._packageId === editPackageId,
);
return app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : undefined;
return app ? appLabel(app) : undefined;
}, [editPackageId, metadataApps, appLabel, t]);

// objectui#11658 — the plan card's extend-mode scope chip names the existing
Expand All @@ -2223,7 +2224,7 @@ export function ChatPane({
const resolveAppLabel = useCallback(
(appName: string) => {
const app = (metadataApps ?? []).find((a) => a.name === appName);
return app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : undefined;
return app ? appLabel(app) : undefined;
},
[metadataApps, appLabel, t],
);
Expand Down Expand Up @@ -2387,7 +2388,7 @@ export function ChatPane({
if (!pkg || byPackage.has(pkg) || isPlatformBuiltinApp(app)) {
continue;
}
byPackage.set(pkg, appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }));
byPackage.set(pkg, appLabel(app));
}
return Array.from(byPackage, ([id, label]) => ({ id, label }));
}, [metadataApps, appLabel, t]);
Expand Down
11 changes: 6 additions & 5 deletions ui/packages/app-shell/src/console/home/AppCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

import { Star, StarOff, ArrowUpRight } from 'lucide-react';
import { Card, CardContent, Button, Badge } from '@object-ui/components';
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
import { resolveKeyedI18nLabel, appRouteSegment } from '../../utils/index.js';
import { useObjectTranslation } from '@object-ui/i18n';
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
import { appRouteSegment } from '../../utils/index.js';
import { useFavorites } from '../../hooks/useFavorites.js';
import { getIcon } from '../../utils/getIcon.js';
import { cn } from '@object-ui/components';
Expand Down Expand Up @@ -40,12 +41,12 @@ function hashStr(s: string): number {

export function AppCard({ app, onClick, isFavorite, index = 0 }: AppCardProps) {
const { t } = useObjectTranslation();
const { appLabel, appDescription } = useObjectLabel();
const { appLabel, appDescription } = useServedAppLabel();
const { toggleFavorite } = useFavorites();

const Icon = getIcon(app.icon);
const label = appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) });
const description = appDescription({ name: app.name, description: resolveKeyedI18nLabel(app.description, t) });
const label = appLabel(app);
const description = appDescription(app);
const accent = ACCENTS[(hashStr(app.name) + index) % ACCENTS.length];

const handleToggleFavorite = (e: React.MouseEvent) => {
Expand Down
8 changes: 4 additions & 4 deletions ui/packages/app-shell/src/console/home/HomeAppsStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ import { useMemo, useState, type ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { LayoutGrid, Plus, Settings, Star } from 'lucide-react';
import { cn } from '@object-ui/components';
import { useDisplayLocale, useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
import { resolveKeyedI18nLabel } from '../../utils/index.js';
import { useDisplayLocale, useObjectTranslation } from '@object-ui/i18n';
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
import { appAddressSegment } from '../../utils/appRoute.js';
import { getIcon } from '../../utils/getIcon.js';
import { timeAgo } from '../../utils/relativeTime.js';
Expand Down Expand Up @@ -168,7 +168,7 @@ export function HomeAppsStrip({
lastOpened?: ReadonlyMap<string, string>;
}) {
const { t } = useObjectTranslation();
const { appLabel } = useObjectLabel();
const { appLabel } = useServedAppLabel();
const displayLocale = useDisplayLocale();
const [showAll, setShowAll] = useState(false);

Expand Down Expand Up @@ -228,7 +228,7 @@ export function HomeAppsStrip({
{/* `-ml-2` puts the first icon under the heading: a tile pads its icon 8 px. */}
<div className="-ml-2 flex flex-wrap gap-1">
{visible.map((app) => {
const label = appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) });
const label = appLabel(app);
const fav = favNames.has(app.name);
const openedAgo = timeAgo(lastOpened?.get(app.name), displayLocale, TILE_OPENED_TIME_FORMAT);
return (
Expand Down
10 changes: 4 additions & 6 deletions ui/packages/app-shell/src/console/home/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import { useNavTargetLabel, type NavTargetLabelOptions } from '../../hooks/useNa
import { readObjectsByName } from '../../hooks/useObjectPresence.js';
import { useFavorites } from '../../hooks/useFavorites.js';
import { useObjectTranslation } from '@object-ui/i18n';
// Its own line: the line above is the pack-hook binding `perm-home-namespace-3546.test.tsx` reads.
import { useObjectLabel } from '@object-ui/i18n';
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
import { useAuth, useWorkspaceAdminStatus } from '@object-ui/auth';
import { useAgents, isAskAgent, agentHasCapability } from '@object-ui/plugin-chatbot/core';
// May this session author metadata? Shared with the chat surfaces since the
Expand All @@ -46,7 +45,6 @@ import {
filterActiveApps,
matchAppBySegment,
resolveHostAppSegment,
resolveKeyedI18nLabel,
} from '../../utils/index.js';
import { useInboxBell } from '../../hooks/useInboxBell.js';
import { openInboxNotification } from '../../layout/openInboxNotification.js';
Expand Down Expand Up @@ -364,7 +362,7 @@ export function HomePage() {
// objectstack#29121 — by name: the object a row names is read alone (as its
// icon below is), so Home never loads the whole registry for its rows.
const targetLabel = useNavTargetLabel(BY_NAME);
const { appLabel } = useObjectLabel();
const { appLabel } = useServedAppLabel();
const { favorites } = useFavorites();
const { user } = useAuth();
const { isAdmin, isResolved: isAdminResolved } = useWorkspaceAdminStatus();
Expand Down Expand Up @@ -509,7 +507,7 @@ export function HomePage() {
item.type === 'record' && objectName
? targetLabel({ kind: 'object', objectName }) ?? objectName
: recentItemTypeLabel(t, item.type),
app: app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : undefined,
app: app ? appLabel(app) : undefined,
icon: typeof objectIcon === 'string' && objectIcon !== '' ? objectIcon : undefined,
};
};
Expand Down Expand Up @@ -804,7 +802,7 @@ export function HomePage() {
settingsHref={setupApp ? setupPath : undefined}
settingsLabel={
setupApp
? appLabel({ name: setupApp.name, label: resolveKeyedI18nLabel(setupApp.label, t) })
? appLabel(setupApp)
: undefined
}
lastOpened={lastOpened}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ import {
} from '@object-ui/components';
import { ArrowLeft, ExternalLink, Download, AlertCircle, Package, Trash2, MoreHorizontal, CheckCircle2, ArrowUpCircle, Database, Loader2 } from 'lucide-react';
import { useWorkspaceAdminStatus } from '@object-ui/auth';
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
import { useObjectTranslation } from '@object-ui/i18n';
import { useDisplayLocale } from '@object-ui/i18n';
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
import { useAdapter } from '@object-ui/react';
import { PackageIcon } from './PackageIcon.js';
import { MarkdownText } from './MarkdownText.js';
Expand Down Expand Up @@ -84,7 +85,7 @@ import { useDependencyMissingText } from './dependencyMissing.js';
import { getCloudBase, getRuntimeConfig, isMarketplaceEnabled } from '../../runtime-config.js';
import { emitMetadataRefresh } from '../../assistant/assistantBus.js';
import { useMetadata } from '../../providers/MetadataProvider.js';
import { appRouteSegment, filterActiveApps, resolveKeyedI18nLabel } from '../../utils/index.js';
import { appRouteSegment, filterActiveApps } from '../../utils/index.js';
import { waitForServedApp, type ServedApp } from './waitForServedApp.js';
import { SuggestedBindingsPanel, type SuggestedBindingsStrings } from '../../components/SuggestedBindingsPanel.js';
import { ConnectorCredentialsStep } from './ConnectorCredentialsStep.js';
Expand Down Expand Up @@ -186,7 +187,7 @@ export function MarketplacePackagePage() {
// The adapter `MetadataProvider` reads through: the deploy wait asks the
// same `GET /meta/app` the cache would, without the cache persisting it.
const adapter = useAdapter();
const { appLabel } = useObjectLabel();
const { appLabel } = useServedAppLabel();
// The runtime's own answer, read once per render -- the same read the
// catalog page makes (objectui#5504). `false` means this runtime mounts no
// marketplace at all, so there is no package to fetch and nothing to
Expand Down Expand Up @@ -969,8 +970,8 @@ export function MarketplacePackagePage() {
<Button size="sm" onClick={() => navigate(`/apps/${appRouteSegment(deployApp)}`)}>
{t('marketplace.install.openApp', {
name: appLabel({
...deployApp,
name: String(deployApp.name ?? appRouteSegment(deployApp)),
label: resolveKeyedI18nLabel(deployApp.label, t),
}),
})}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
Package, Search, Store, AlertCircle, CheckCircle2, Settings, Loader2, ChevronDown, Copy, Check,
} from 'lucide-react';
import { useWorkspaceAdminStatus } from '@object-ui/auth';
import { useDisplayLocale, useObjectLabel, useObjectTranslation } from '@object-ui/i18n';
import { useDisplayLocale, useObjectTranslation } from '@object-ui/i18n';
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
import { useAdapter } from '@object-ui/react';
import { PackageIcon } from './PackageIcon.js';
import { MarketplaceAccessDenied } from './MarketplaceAccessDenied.js';
Expand All @@ -47,7 +48,7 @@ import {
import { getCloudBase, getRuntimeConfig, isMarketplaceEnabled } from '../../runtime-config.js';
import { PageHeader } from '../../layout/PageHeader.js';
import { emitMetadataRefresh } from '../../assistant/assistantBus.js';
import { appRouteSegment, filterActiveApps, resolveKeyedI18nLabel } from '../../utils/index.js';
import { appRouteSegment, filterActiveApps } from '../../utils/index.js';
import { waitForServedApp, type ServedApp } from './waitForServedApp.js';
import { splitPackageSuffix } from './middleTruncate.js';
import { useDependencyMissingText } from './dependencyMissing.js';
Expand Down Expand Up @@ -309,7 +310,7 @@ export function MarketplacePage() {
const { isAdmin, isResolved } = useWorkspaceAdminStatus();
const { t, language } = useObjectTranslation();
const dependencyMissingText = useDependencyMissingText();
const { appLabel } = useObjectLabel();
const { appLabel } = useServedAppLabel();
// The adapter `MetadataProvider` reads through: the deploy wait asks the
// same `GET /meta/app` the cache would, without the cache persisting it.
const adapter = useAdapter();
Expand Down Expand Up @@ -619,8 +620,8 @@ export function MarketplacePage() {
<Button size="sm" onClick={() => navigate(`/apps/${appRouteSegment(orgDeployApp)}`)}>
{t('marketplace.install.openApp', {
name: appLabel({
...orgDeployApp,
name: String(orgDeployApp.name ?? appRouteSegment(orgDeployApp)),
label: resolveKeyedI18nLabel(orgDeployApp.label, t),
}),
})}
</Button>
Expand Down
Loading
Loading