Skip to content
Draft
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
2 changes: 1 addition & 1 deletion apps/console/vercel.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"installCommand": "cd ../.. && pnpm install --frozen-lockfile",
"buildCommand": "cd ../.. && pnpm turbo run build --filter=@object-ui/console^... && cd apps/console && VITE_BASE_PATH=/ pnpm build:vercel",
"buildCommand": "cd apps/console && pnpm msw:init && NODE_OPTIONS=--max-old-space-size=4096 VITE_BASE_PATH=/ VITE_USE_MOCK_SERVER=true vite build",
"outputDirectory": "dist",
"framework": "vite",
"rewrites": [
Expand Down
122 changes: 68 additions & 54 deletions apps/console/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,53 @@ function preloadCriticalChunks(): Plugin {
// auto-mount slug. Override with VITE_BASE_PATH only if deploying standalone.
const basePath = process.env.VITE_BASE_PATH || '/console/';

// On Vercel/CI we skip the compression and visualizer plugins because the
// Vercel CDN handles gzip/brotli automatically and bundle analysis is not
// needed during CI builds. This reduces peak memory by ~1.5 GB.
//
// Workspace src/ aliases are kept in ALL environments (dev + CI) so that
// plugin side-effect imports (ComponentRegistry.register) resolve correctly.
// Without them, Vite would import pre-built dist/ bundles where the
// singleton ComponentRegistry can get duplicated across chunks, causing
// "Unknown component type" errors at runtime.
const isCI = !!(process.env.VERCEL || process.env.CI);

// Workspace src/ aliases — gives instant HMR in dev and ensures correct
// side-effect resolution (plugin registrations) in production builds.
const workspaceAliases: Record<string, string> = {
'@object-ui/components': path.resolve(__dirname, '../../packages/components/src'),
'@object-ui/core': path.resolve(__dirname, '../../packages/core/src'),
'@object-ui/fields': path.resolve(__dirname, '../../packages/fields/src'),
'@object-ui/layout': path.resolve(__dirname, '../../packages/layout/src'),
'@object-ui/plugin-dashboard': path.resolve(__dirname, '../../packages/plugin-dashboard/src'),
'@object-ui/plugin-report': path.resolve(__dirname, '../../packages/plugin-report/src'),
'@object-ui/plugin-form': path.resolve(__dirname, '../../packages/plugin-form/src'),
'@object-ui/plugin-grid': path.resolve(__dirname, '../../packages/plugin-grid/src'),
'@object-ui/react': path.resolve(__dirname, '../../packages/react/src'),
'@object-ui/types': path.resolve(__dirname, '../../packages/types/src'),
'@object-ui/data-objectstack': path.resolve(__dirname, '../../packages/data-objectstack/src'),
'@object-ui/auth': path.resolve(__dirname, '../../packages/auth/src'),
'@object-ui/permissions': path.resolve(__dirname, '../../packages/permissions/src'),
'@object-ui/collaboration': path.resolve(__dirname, '../../packages/collaboration/src'),
'@object-ui/tenant': path.resolve(__dirname, '../../packages/tenant/src'),
'@object-ui/i18n': path.resolve(__dirname, '../../packages/i18n/src'),

// Plugin Aliases
'@object-ui/plugin-aggrid': path.resolve(__dirname, '../../packages/plugin-aggrid/src'),
'@object-ui/plugin-calendar': path.resolve(__dirname, '../../packages/plugin-calendar/src'),
'@object-ui/plugin-charts': path.resolve(__dirname, '../../packages/plugin-charts/src'),
'@object-ui/plugin-chatbot': path.resolve(__dirname, '../../packages/plugin-chatbot/src'),
'@object-ui/plugin-detail': path.resolve(__dirname, '../../packages/plugin-detail/src'),
'@object-ui/plugin-editor': path.resolve(__dirname, '../../packages/plugin-editor/src'),
'@object-ui/plugin-gantt': path.resolve(__dirname, '../../packages/plugin-gantt/src'),
'@object-ui/plugin-kanban': path.resolve(__dirname, '../../packages/plugin-kanban/src'),
'@object-ui/plugin-list': path.resolve(__dirname, '../../packages/plugin-list/src'),
'@object-ui/plugin-map': path.resolve(__dirname, '../../packages/plugin-map/src'),
'@object-ui/plugin-markdown': path.resolve(__dirname, '../../packages/plugin-markdown/src'),
'@object-ui/plugin-timeline': path.resolve(__dirname, '../../packages/plugin-timeline/src'),
'@object-ui/plugin-view': path.resolve(__dirname, '../../packages/plugin-view/src'),
};

// https://vitejs.dev/config/
export default defineConfig({
base: basePath,
Expand All @@ -54,63 +101,30 @@ export default defineConfig({
react(),
// Inject <link rel="modulepreload"> for critical chunks
preloadCriticalChunks(),
// Gzip compression for production assets
compression({
algorithm: 'gzip',
exclude: [/\.(br)$/, /\.(gz)$/],
threshold: 1024,
}),
// Brotli compression for modern browsers
compression({
algorithm: 'brotliCompress',
exclude: [/\.(br)$/, /\.(gz)$/],
threshold: 1024,
}),
// Bundle analysis (generates stats.html in dist/)
visualizer({
filename: 'dist/stats.html',
gzipSize: true,
brotliSize: true,
open: false,
}),
// Gzip/Brotli compression & bundle visualizer are skipped on Vercel/CI to
// reduce memory usage — Vercel's CDN compresses assets automatically.
...(!isCI ? [
compression({
algorithm: 'gzip',
exclude: [/\.(br)$/, /\.(gz)$/],
threshold: 1024,
}),
compression({
algorithm: 'brotliCompress',
exclude: [/\.(br)$/, /\.(gz)$/],
threshold: 1024,
}),
visualizer({
filename: 'dist/stats.html',
gzipSize: true,
brotliSize: true,
open: false,
}),
] : []),
],
resolve: {
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'],
alias: {
'@object-ui/components': path.resolve(__dirname, '../../packages/components/src'),
'@object-ui/core': path.resolve(__dirname, '../../packages/core/src'),
'@object-ui/fields': path.resolve(__dirname, '../../packages/fields/src'),
'@object-ui/layout': path.resolve(__dirname, '../../packages/layout/src'),
'@object-ui/plugin-dashboard': path.resolve(__dirname, '../../packages/plugin-dashboard/src'),
'@object-ui/plugin-report': path.resolve(__dirname, '../../packages/plugin-report/src'),
'@object-ui/plugin-form': path.resolve(__dirname, '../../packages/plugin-form/src'),
'@object-ui/plugin-grid': path.resolve(__dirname, '../../packages/plugin-grid/src'),
'@object-ui/react': path.resolve(__dirname, '../../packages/react/src'),
'@object-ui/types': path.resolve(__dirname, '../../packages/types/src'),
'@object-ui/data-objectstack': path.resolve(__dirname, '../../packages/data-objectstack/src'),
'@object-ui/auth': path.resolve(__dirname, '../../packages/auth/src'),
'@object-ui/permissions': path.resolve(__dirname, '../../packages/permissions/src'),
'@object-ui/collaboration': path.resolve(__dirname, '../../packages/collaboration/src'),
'@object-ui/tenant': path.resolve(__dirname, '../../packages/tenant/src'),
'@object-ui/i18n': path.resolve(__dirname, '../../packages/i18n/src'),

// Missing Plugin Aliases
'@object-ui/plugin-aggrid': path.resolve(__dirname, '../../packages/plugin-aggrid/src'),
'@object-ui/plugin-calendar': path.resolve(__dirname, '../../packages/plugin-calendar/src'),
'@object-ui/plugin-charts': path.resolve(__dirname, '../../packages/plugin-charts/src'),
'@object-ui/plugin-chatbot': path.resolve(__dirname, '../../packages/plugin-chatbot/src'),
'@object-ui/plugin-detail': path.resolve(__dirname, '../../packages/plugin-detail/src'),
'@object-ui/plugin-editor': path.resolve(__dirname, '../../packages/plugin-editor/src'),
'@object-ui/plugin-gantt': path.resolve(__dirname, '../../packages/plugin-gantt/src'),
'@object-ui/plugin-kanban': path.resolve(__dirname, '../../packages/plugin-kanban/src'),
'@object-ui/plugin-list': path.resolve(__dirname, '../../packages/plugin-list/src'),
'@object-ui/plugin-map': path.resolve(__dirname, '../../packages/plugin-map/src'),
'@object-ui/plugin-markdown': path.resolve(__dirname, '../../packages/plugin-markdown/src'),
'@object-ui/plugin-timeline': path.resolve(__dirname, '../../packages/plugin-timeline/src'),
'@object-ui/plugin-view': path.resolve(__dirname, '../../packages/plugin-view/src'),


},
alias: workspaceAliases,
},
optimizeDeps: {
include: [
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
Expand Down
3 changes: 2 additions & 1 deletion packages/collaboration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"scripts": {
Expand Down
6 changes: 4 additions & 2 deletions packages/i18n/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./locales/*": {
"types": "./dist/locales/*.d.ts",
"import": "./dist/locales/*.js"
"import": "./dist/locales/*.js",
"default": "./dist/locales/*.js"
}
},
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion packages/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
Expand Down
3 changes: 2 additions & 1 deletion packages/permissions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-dashboard/src/WidgetConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ export function WidgetConfigPanel({
availableFields,
}: WidgetConfigPanelProps) {
// Pre-process config to resolve any I18nLabel values for title/description
const normalizedConfig = React.useMemo(() => ({
const normalizedConfig: Record<string, any> = React.useMemo(() => ({
...config,
title: typeof config.title === 'object' ? resolveLabel(config.title) : config.title,
description: typeof config.description === 'object' ? resolveLabel(config.description) : config.description,
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-dashboard/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

/** Returns true when the widget data config uses provider: 'object' (async data source). */
export function isObjectProvider(widgetData: unknown): widgetData is { provider: 'object'; object?: string; aggregate?: any } {
export function isObjectProvider(widgetData: unknown): widgetData is { provider: 'object'; object?: string; aggregate?: any; filter?: any } {
return (
widgetData != null &&
typeof widgetData === 'object' &&
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-detail/src/DetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
}, [schema.autoDiscoverRelated, schema.related, objectSchema]);

// Merge explicit and auto-discovered related lists
const effectiveRelated = React.useMemo(() => {
const effectiveRelated: NonNullable<DetailViewSchema['related']> = React.useMemo(() => {
if (schema.related && schema.related.length > 0) return schema.related;
return discoveredRelated.map((r) => ({
title: r.title,
Expand Down
10 changes: 9 additions & 1 deletion packages/plugin-grid/src/ObjectGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import React, { useEffect, useState, useCallback, useMemo } from 'react';
import type { ObjectGridSchema, DataSource, ListColumn, ViewData } from '@object-ui/types';
import type { I18nLabel } from '@objectstack/spec/ui';
import { SchemaRenderer, useDataScope, useNavigationOverlay, useAction, useObjectTranslation, useSafeFieldLabel } from '@object-ui/react';
import { getCellRenderer, formatCurrency, formatCompactCurrency, formatDate, formatPercent, humanizeLabel } from '@object-ui/fields';
import {
Expand Down Expand Up @@ -90,6 +91,13 @@ function useGridTranslation() {
}
}

/** Resolve an I18nLabel (string | {key, defaultValue}) to a plain string. */
function resolveColumnLabel(label: string | I18nLabel | undefined): string | undefined {
if (label == null) return undefined;
if (typeof label === 'string') return label;
return label.defaultValue || label.key;
}

export interface ObjectGridProps {
schema: ObjectGridSchema;
dataSource?: DataSource;
Expand Down Expand Up @@ -619,7 +627,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
return (cols as ListColumn[])
.filter((col) => col?.field && typeof col.field === 'string' && !col.hidden)
.map((col, colIndex) => {
const rawHeader = col.label || col.field.charAt(0).toUpperCase() + col.field.slice(1).replace(/_/g, ' ');
const rawHeader = resolveColumnLabel(col.label) || col.field.charAt(0).toUpperCase() + col.field.slice(1).replace(/_/g, ' ');
const header = schema.objectName ? resolveFieldLabel(schema.objectName, col.field, rawHeader) : rawHeader;

// Build custom cell renderer based on column configuration
Expand Down
3 changes: 2 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion packages/tenant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
Expand Down
10 changes: 10 additions & 0 deletions packages/types/src/complex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,16 @@ export interface DashboardWidgetSchema {
* Aligned with @objectstack/spec DashboardWidgetSchema.responsive.
*/
responsive?: any;
/**
* Enable search input for table-type widgets.
* @default false
*/
searchable?: boolean;
/**
* Enable pagination for table-type widgets.
* @default false
*/
pagination?: boolean;
/**
* ARIA accessibility attributes.
* Aligned with @objectstack/spec AriaPropsSchema.
Expand Down