Skip to content

Commit 1afdb33

Browse files
authored
Merge pull request #880 from objectstack-ai/copilot/fix-ci-errors-a2dfe0e6-f121-49c7-b4ed-39b43102d39e
2 parents 7fd20e4 + 50adaba commit 1afdb33

File tree

6 files changed

+42
-25
lines changed

6 files changed

+42
-25
lines changed

apps/console/src/__tests__/SpecCompliance.test.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ describe('ObjectStack Spec v0.9.0 Compliance', () => {
2525
expect(app.name).toBeDefined();
2626
expect(typeof app.name).toBe('string');
2727
expect(app.label).toBeDefined();
28-
expect(typeof app.label).toBe('string');
28+
expect(['string', 'object']).toContain(typeof app.label);
29+
if (typeof app.label === 'object') {
30+
expect(app.label).toHaveProperty('key');
31+
}
2932

3033
// Name convention: lowercase snake_case
3134
expect(app.name).toMatch(/^[a-z][a-z0-9_]*$/);
@@ -36,7 +39,10 @@ describe('ObjectStack Spec v0.9.0 Compliance', () => {
3639

3740
// Optional fields that should be defined if present
3841
if (app.description) {
39-
expect(typeof app.description).toBe('string');
42+
expect(['string', 'object']).toContain(typeof app.description);
43+
if (typeof app.description === 'object') {
44+
expect(app.description).toHaveProperty('key');
45+
}
4046
}
4147
if (app.version) {
4248
expect(typeof app.version).toBe('string');

apps/console/src/components/AppSidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ export function AppSidebar({ activeAppName, onAppChange }: { activeAppName: stri
301301
<div className="flex size-6 items-center justify-center rounded-sm border">
302302
{app.icon ? React.createElement(getIcon(app.icon), { className: "size-3" }) : <Database className="size-3" />}
303303
</div>
304-
{app.label}
304+
{resolveI18nLabel(app.label, t)}
305305
{activeApp.name === app.name && <span className="ml-auto text-xs"></span>}
306306
</DropdownMenuItem>
307307
))}
@@ -530,7 +530,7 @@ export function AppSidebar({ activeAppName, onAppChange }: { activeAppName: stri
530530
return (
531531
<Link key={item.id} to={href} className="flex flex-col items-center gap-0.5 px-2 py-1.5 text-muted-foreground hover:text-foreground transition-colors min-w-[44px] min-h-[44px] justify-center">
532532
<NavIcon className="h-5 w-5" />
533-
<span className="text-[10px] truncate max-w-[60px]">{item.label}</span>
533+
<span className="text-[10px] truncate max-w-[60px]">{resolveI18nLabel(item.label, t)}</span>
534534
</Link>
535535
);
536536
})}

apps/console/src/components/CommandPalette.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { useTheme } from './theme-provider';
3434
import { useExpressionContext, evaluateVisibility } from '../context/ExpressionProvider';
3535
import { useObjectTranslation } from '@object-ui/i18n';
36+
import { resolveI18nLabel } from '../utils';
3637

3738
/** Resolve a Lucide icon by name (kebab-case or PascalCase) */
3839
function getIcon(name?: string): React.ElementType {
@@ -101,11 +102,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange
101102
return (
102103
<CommandItem
103104
key={item.id}
104-
value={`object ${item.label} ${item.objectName}`}
105+
value={`object ${resolveI18nLabel(item.label, t)} ${item.objectName}`}
105106
onSelect={() => runCommand(() => navigate(`${baseUrl}/${item.objectName}`))}
106107
>
107108
<Icon className="mr-2 h-4 w-4" />
108-
<span>{item.label}</span>
109+
<span>{resolveI18nLabel(item.label, t)}</span>
109110
</CommandItem>
110111
);
111112
})}
@@ -120,11 +121,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange
120121
.map(item => (
121122
<CommandItem
122123
key={item.id}
123-
value={`dashboard ${item.label} ${item.dashboardName}`}
124+
value={`dashboard ${resolveI18nLabel(item.label, t)} ${item.dashboardName}`}
124125
onSelect={() => runCommand(() => navigate(`${baseUrl}/dashboard/${item.dashboardName}`))}
125126
>
126127
<LayoutDashboard className="mr-2 h-4 w-4" />
127-
<span>{item.label}</span>
128+
<span>{resolveI18nLabel(item.label, t)}</span>
128129
</CommandItem>
129130
))}
130131
</CommandGroup>
@@ -138,11 +139,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange
138139
.map(item => (
139140
<CommandItem
140141
key={item.id}
141-
value={`page ${item.label} ${item.pageName}`}
142+
value={`page ${resolveI18nLabel(item.label, t)} ${item.pageName}`}
142143
onSelect={() => runCommand(() => navigate(`${baseUrl}/page/${item.pageName}`))}
143144
>
144145
<FileText className="mr-2 h-4 w-4" />
145-
<span>{item.label}</span>
146+
<span>{resolveI18nLabel(item.label, t)}</span>
146147
</CommandItem>
147148
))}
148149
</CommandGroup>
@@ -156,11 +157,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange
156157
.map(item => (
157158
<CommandItem
158159
key={item.id}
159-
value={`report ${item.label} ${item.reportName}`}
160+
value={`report ${resolveI18nLabel(item.label, t)} ${item.reportName}`}
160161
onSelect={() => runCommand(() => navigate(`${baseUrl}/report/${item.reportName}`))}
161162
>
162163
<BarChart3 className="mr-2 h-4 w-4" />
163-
<span>{item.label}</span>
164+
<span>{resolveI18nLabel(item.label, t)}</span>
164165
</CommandItem>
165166
))}
166167
</CommandGroup>
@@ -178,11 +179,11 @@ export function CommandPalette({ apps, activeApp, objects: _objects, onAppChange
178179
return (
179180
<CommandItem
180181
key={app.name}
181-
value={`app ${app.label} ${app.name}`}
182+
value={`app ${resolveI18nLabel(app.label, t)} ${app.name}`}
182183
onSelect={() => runCommand(() => onAppChange(app.name))}
183184
>
184185
<Icon className="mr-2 h-4 w-4" />
185-
<span>{app.label}</span>
186+
<span>{resolveI18nLabel(app.label, t)}</span>
186187
{app.name === activeApp?.name && (
187188
<span className="ml-auto text-xs text-muted-foreground">{t('console.commandPalette.current')}</span>
188189
)}

apps/console/src/components/ConsoleLayout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { AppShell } from '@object-ui/layout';
1111
import { AppSidebar } from './AppSidebar';
1212
import { AppHeader } from './AppHeader';
1313
import { useResponsiveSidebar } from '../hooks/useResponsiveSidebar';
14+
import { resolveI18nLabel } from '../utils';
1415
import type { ConnectionState } from '../dataSource';
1516

1617
interface ConsoleLayoutProps {
@@ -46,7 +47,7 @@ export function ConsoleLayout({
4647
}
4748
navbar={
4849
<AppHeader
49-
appName={activeApp?.label || activeAppName}
50+
appName={resolveI18nLabel(activeApp?.label) || activeAppName}
5051
objects={objects}
5152
connectionState={connectionState}
5253
/>
@@ -60,7 +61,7 @@ export function ConsoleLayout({
6061
favicon: activeApp.branding.favicon,
6162
logo: activeApp.branding.logo,
6263
title: activeApp.label
63-
? `${activeApp.label} — ObjectStack Console`
64+
? `${resolveI18nLabel(activeApp.label)} — ObjectStack Console`
6465
: undefined,
6566
}
6667
: undefined

apps/console/src/pages/system/AppManagementPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from 'lucide-react';
2828
import { toast } from 'sonner';
2929
import { useMetadata } from '../../context/MetadataProvider';
30+
import { resolveI18nLabel } from '../../utils';
3031

3132
export function AppManagementPage() {
3233
const navigate = useNavigate();
@@ -208,14 +209,14 @@ export function AppManagementPage() {
208209
</div>
209210
<div className="min-w-0 flex-1">
210211
<div className="flex items-center gap-2">
211-
<span className="font-medium truncate">{app.label || app.name}</span>
212+
<span className="font-medium truncate">{resolveI18nLabel(app.label) || app.name}</span>
212213
{isDefault && <Badge variant="default" className="text-xs">Default</Badge>}
213214
<Badge variant={isActive ? 'secondary' : 'outline'} className="text-xs">
214215
{isActive ? 'Active' : 'Inactive'}
215216
</Badge>
216217
</div>
217218
{app.description && (
218-
<p className="text-xs text-muted-foreground truncate">{app.description}</p>
219+
<p className="text-xs text-muted-foreground truncate">{resolveI18nLabel(app.description)}</p>
219220
)}
220221
</div>
221222
<div className="flex items-center gap-1 shrink-0">

packages/plugin-dashboard/src/DashboardRenderer.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ import { forwardRef, useState, useEffect, useCallback, useRef } from 'react';
1313
import { RefreshCw } from 'lucide-react';
1414
import { isObjectProvider } from './utils';
1515

16+
/** Resolve an I18nLabel (string or {key, defaultValue}) to a plain string. */
17+
function resolveLabel(label: string | { key?: string; defaultValue?: string } | undefined): string | undefined {
18+
if (label === undefined || label === null) return undefined;
19+
if (typeof label === 'string') return label;
20+
return label.defaultValue || label.key;
21+
}
22+
1623
// Color palette for charts
1724
const CHART_COLORS = [
1825
'hsl(var(--chart-1))',
@@ -248,7 +255,8 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
248255

249256
const componentSchema = getComponentSchema();
250257
const isSelfContained = widget.type === 'metric';
251-
const widgetKey = widget.id || widget.title || `widget-${index}`;
258+
const resolvedTitle = resolveLabel(widget.title);
259+
const widgetKey = widget.id || resolvedTitle || `widget-${index}`;
252260
const isSelected = designMode && selectedWidgetId === widget.id;
253261

254262
const designModeProps = designMode ? {
@@ -257,7 +265,7 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
257265
role: 'button' as const,
258266
tabIndex: 0,
259267
'aria-selected': isSelected,
260-
'aria-label': `Widget: ${widget.title || `Widget ${index + 1}`}`,
268+
'aria-label': `Widget: ${resolvedTitle || `Widget ${index + 1}`}`,
261269
onClick: (e: React.MouseEvent) => handleWidgetClick(e, widget.id),
262270
onKeyDown: (e: React.KeyboardEvent) => handleWidgetKeyDown(e, widget.id, index),
263271
} : {};
@@ -305,10 +313,10 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
305313
}: undefined}
306314
{...designModeProps}
307315
>
308-
{widget.title && (
316+
{resolvedTitle && (
309317
<CardHeader className="pb-2 border-b border-border/40 bg-muted/20 px-3 sm:px-6">
310-
<CardTitle className="text-sm sm:text-base font-medium tracking-tight truncate" title={widget.title}>
311-
{widget.title}
318+
<CardTitle className="text-sm sm:text-base font-medium tracking-tight truncate" title={resolvedTitle}>
319+
{resolvedTitle}
312320
</CardTitle>
313321
</CardHeader>
314322
)}
@@ -325,10 +333,10 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
325333
const headerSection = schema.header && (
326334
<div className="col-span-full mb-4">
327335
{schema.header.showTitle !== false && schema.title && (
328-
<h2 className="text-lg font-semibold tracking-tight">{schema.title}</h2>
336+
<h2 className="text-lg font-semibold tracking-tight">{resolveLabel(schema.title)}</h2>
329337
)}
330338
{schema.header.showDescription !== false && schema.description && (
331-
<p className="text-sm text-muted-foreground mt-1">{schema.description}</p>
339+
<p className="text-sm text-muted-foreground mt-1">{resolveLabel(schema.description)}</p>
332340
)}
333341
{schema.header.actions && schema.header.actions.length > 0 && (
334342
<div className="flex gap-2 mt-3">

0 commit comments

Comments
 (0)