Uh oh!
There was an error while loading. Please reload this page.
Extend schema expressiveness for HeaderBar, SidebarNav, BreadcrumbItem, ViewSwitcher; fix i18n across data-table, ListView toolbar, and ObjectGrid - #905
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…, SidebarNav; add i18n for data-table pagination Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
hotlong
commented
Feb 28, 2026
仔细评估是否已经完成要求的所有开发 @copilot |
…lace all hardcoded English labels with t() calls Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
经过全面评估,之前的实现缺少了 B4 和 B6 的关键部分: B4 缺失部分(已在 72e8801 修复): ListView 工具栏按钮(Filter、Group、Sort、Export、Color、Search)及弹出框标题(Filter Records、Group By、Sort Records、Row Color 等)全部是硬编码英文,未通过
B6 缺失部分(已在 72e8801 修复): ObjectGrid 没有导入或使用任何 i18n——"Error loading grid"、"Loading grid..."、"Actions"、"Export"、"Pull to refresh" 等全部硬编码。现已:
所有 6 个问题(B1-B6)现已完整实现。692 个测试全部通过,构建成功,CodeQL 无安全告警。 |
Uh oh!
There was an error while loading. Please reload this page.
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
There was a problem hiding this comment.
Pull request overview
Extends ObjectUI’s schema/types to express previously hardcoded Console navigation features (HeaderBar crumbs/search/actions, breadcrumb sibling switching, ViewSwitcher create/actions) and standardizes i18n for list/table/grid UI so non-en locales don’t show mixed-language strings.
Changes:
- Extended schema types + Zod validators for
HeaderBarSchema,BreadcrumbItem, andViewSwitcherSchemato support new navigation capabilities. - Added schema-driven rendering for HeaderBar crumbs (with sibling dropdown), search slot, and action/right-content slots; added ViewSwitcher create button + per-view action icons.
- Replaced hardcoded English strings in ListView toolbar / DataTable pagination / ObjectGrid UI with i18n calls and updated all locale packs accordingly.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/types/src/zod/views.zod.ts | Adds Zod validation for allowCreateView and viewActions in ViewSwitcherSchema. |
| packages/types/src/zod/navigation.zod.ts | Adds BreadcrumbItemSchema (with siblings) and extends HeaderBarSchema Zod fields (crumbs/search/actions/rightContent). |
| packages/types/src/views.ts | Extends ViewSwitcherSchema type with allowCreateView + viewActions. |
| packages/types/src/navigation.ts | Extends HeaderBarSchema with crumbs/search/actions/rightContent and BreadcrumbItem with siblings. |
| packages/plugin-view/src/ViewSwitcher.tsx | Renders create-view button and per-view action buttons driven by schema; adds related props/callbacks. |
| packages/plugin-list/src/ListView.tsx | Replaces toolbar hardcoded strings with useListViewTranslation() keys. |
| packages/plugin-grid/src/ObjectGrid.tsx | Adds useGridTranslation() wrapper and replaces hardcoded grid strings with t('grid.*'). |
| packages/layout/src/SidebarNav.tsx | Adds badges, nested children via collapsible submenus, and optional client-side search filter. |
| packages/components/src/renderers/navigation/header-bar.tsx | Adds schema-driven crumbs (with sibling dropdown), inline search slot, and action/rightContent rendering via SchemaRenderer. |
| packages/components/src/renderers/complex/data-table.tsx | Adds useTableTranslation() and i18n for pagination footer strings. |
| packages/i18n/src/locales/en.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/zh.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/ar.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/de.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/es.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/fr.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/ja.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/ko.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/pt.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| packages/i18n/src/locales/ru.ts | Adds table.pageInfo/table.totalRecords and new list.* toolbar keys. |
| const createViewButton = schema.allowCreateView ? ( | ||
| <Button | ||
| type="button" | ||
| variant="ghost" | ||
| size="icon-sm" | ||
| onClick={() => onCreateView?.()} | ||
| title={createViewLabel} | ||
| > |
There was a problem hiding this comment.
The create-view button can be rendered even when onCreateView is not provided, resulting in a visible control that does nothing. Consider hiding it unless a handler is present, or disabling it (and providing a disabled tooltip) when onCreateView is undefined.
| const viewActionButtons = schema.viewActions && schema.viewActions.length > 0 ? ( | ||
| <div className="flex items-center gap-1"> | ||
| {schema.viewActions.map((action, idx) => { | ||
| const ActionIcon = action.icon | ||
| ? resolveIcon(action.icon) || DEFAULT_VIEW_ACTION_ICONS[action.type] | ||
| : DEFAULT_VIEW_ACTION_ICONS[action.type]; | ||
| return ( | ||
| <Button | ||
| key={`action-${action.type}-${idx}`} | ||
| type="button" | ||
| variant="ghost" | ||
| size="icon-sm" | ||
| onClick={() => onViewAction?.(action.type, currentView!)} | ||
| title={DEFAULT_VIEW_ACTION_LABELS[action.type] || action.type} | ||
| > | ||
| {ActionIcon ? <ActionIcon className="h-3.5 w-3.5" /> : null} | ||
| </Button> | ||
| ); | ||
| })} | ||
| </div> | ||
| ) : null; | ||
| const createViewButton = schema.allowCreateView ? ( | ||
| <Button | ||
| type="button" | ||
| variant="ghost" | ||
| size="icon-sm" | ||
| onClick={() => onCreateView?.()} | ||
| title={createViewLabel} | ||
| > | ||
| <Plus className="h-3.5 w-3.5" /> | ||
| </Button> | ||
| ) : null; | ||
There was a problem hiding this comment.
There are existing tests in packages/plugin-view/src/__tests__, but the new allowCreateView / viewActions rendering paths aren’t covered. Adding a focused test for (1) showing/hiding the "+" button, and (2) clicking an action invoking onViewAction with the correct args would prevent regressions.
| ); | ||
| } | ||
| export function SidebarNav({ items, title = "Application", className, collapsible = "icon", searchEnabled = false, searchPlaceholder = "Search..." }: SidebarNavProps) { |
There was a problem hiding this comment.
searchPlaceholder defaults to the hardcoded English string "Search...". Since this component is used in locale-aware UIs, the default can reintroduce mixed-language output if callers forget to override it. Consider defaulting to an empty string/undefined (and requiring callers to pass a localized placeholder) or wiring this through the same i18n fallback pattern used elsewhere.
| exportfunctionSidebarNav({ items, title ="Application", className, collapsible ="icon", searchEnabled =false, searchPlaceholder ="Search..."}: SidebarNavProps){ | |
| exportfunctionSidebarNav({ items, title ="Application", className, collapsible ="icon", searchEnabled =false, searchPlaceholder }: SidebarNavProps){ |
| variant="ghost" | ||
| size="icon-sm" | ||
| onClick={() => onCreateView?.()} | ||
| title={createViewLabel} |
There was a problem hiding this comment.
The create-view icon button is missing an accessible label (same issue as the per-view action icons). Add aria-label or a visually-hidden label so screen readers can announce what the “+” button does.
| title={createViewLabel} | |
| title={createViewLabel} | |
| aria-label={createViewLabel} |
| return flatItems.map(group => ({ | ||
| ...group, | ||
| items: group.items.filter(item => | ||
| item.title.toLowerCase().includes(lowerSearch) || | ||
| item.children?.some(child => child.title.toLowerCase().includes(lowerSearch)) | ||
| ), | ||
| })).filter(group => group.items.length > 0); |
There was a problem hiding this comment.
The search filter includes a parent item when any child matches, but NavItemRenderer still renders all children (including ones that don’t match the query). This makes the search results noisy/inaccurate for nested navigation. Consider filtering item.children to just the matching children (or highlighting matches) when search is non-empty.
| returnflatItems.map(group=>({ | |
| ...group, | |
| items: group.items.filter(item=> | |
| item.title.toLowerCase().includes(lowerSearch)|| | |
| item.children?.some(child=>child.title.toLowerCase().includes(lowerSearch)) | |
| ), | |
| })).filter(group=>group.items.length>0); | |
| returnflatItems | |
| .map(group=>{ | |
| constfilteredItems: NavItem[]=group.items | |
| .map((item)=>{ | |
| consttitleMatch=item.title.toLowerCase().includes(lowerSearch); | |
| constfilteredChildren=item.children?.filter((child)=> | |
| child.title.toLowerCase().includes(lowerSearch) | |
| ); | |
| consthasChildMatch=!!filteredChildren&&filteredChildren.length>0; | |
| if(!titleMatch&&!hasChildMatch){ | |
| returnnull; | |
| } | |
| return{ | |
| ...item, | |
| // When search is active, only keep matching children (if any). | |
| children: filteredChildren&&filteredChildren.length>0 ? filteredChildren : undefined, | |
| }; | |
| }) | |
| .filter((item): item is NavItem=>item!==null); | |
| return{ | |
| ...group, | |
| items: filteredItems, | |
| }; | |
| }) | |
| .filter(group=>group.items.length>0); |
| <div className="ml-auto flex items-center gap-2"> | ||
| {schema.search?.enabled && ( | ||
| <div className="relative"> | ||
| <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" /> | ||
| <Input | ||
| type="search" | ||
| placeholder={schema.search.placeholder} | ||
| className="pl-8 w-[200px] lg:w-[300px]" | ||
| /> |
There was a problem hiding this comment.
The new search slot renders an <Input> but doesn’t expose any way for schema/consumers to receive the query (no value, onChange, binding, or action dispatch). As-is, it’s a “dead” input that won’t affect application state. Consider adding a schema-level callback/event (e.g., onSearchChange action name) or a bind path so the input is actually usable.
| try { | ||
| const result = useObjectTranslation(); | ||
| const testValue = result.t('grid.actions'); | ||
| if (testValue === 'grid.actions') { | ||
| return { | ||
| t: (key: string, options?: Record<string, unknown>) => { | ||
| let value = GRID_DEFAULT_TRANSLATIONS[key] || key; | ||
| if (options) { | ||
| for (const [k, v] of Object.entries(options)) { | ||
| value = value.replace(`{{${k}}}`, String(v)); | ||
| } | ||
| } | ||
| return value; | ||
| }, | ||
| }; | ||
| } | ||
| return { t: result.t }; | ||
| } catch { | ||
| return { | ||
| t: (key: string, options?: Record<string, unknown>) => { | ||
| let value = GRID_DEFAULT_TRANSLATIONS[key] || key; | ||
| if (options) { | ||
| for (const [k, v] of Object.entries(options)) { | ||
| value = value.replace(`{{${k}}}`, String(v)); | ||
| } | ||
| } | ||
| return value; | ||
| }, |
There was a problem hiding this comment.
useGridTranslation duplicates the same fallback/interpolation logic already implemented in useListViewTranslation and useTableTranslation. Consider extracting a small shared helper (e.g., in @object-ui/react or a local utility) to keep interpolation behavior consistent and avoid having to update multiple copies when translation behavior changes.
| try{ | |
| constresult=useObjectTranslation(); | |
| consttestValue=result.t('grid.actions'); | |
| if(testValue==='grid.actions'){ | |
| return{ | |
| t: (key: string,options?: Record<string,unknown>)=>{ | |
| letvalue=GRID_DEFAULT_TRANSLATIONS[key]||key; | |
| if(options){ | |
| for(const[k,v]ofObject.entries(options)){ | |
| value=value.replace(`{{${k}}}`,String(v)); | |
| } | |
| } | |
| returnvalue; | |
| }, | |
| }; | |
| } | |
| return{t: result.t}; | |
| }catch{ | |
| return{ | |
| t: (key: string,options?: Record<string,unknown>)=>{ | |
| letvalue=GRID_DEFAULT_TRANSLATIONS[key]||key; | |
| if(options){ | |
| for(const[k,v]ofObject.entries(options)){ | |
| value=value.replace(`{{${k}}}`,String(v)); | |
| } | |
| } | |
| returnvalue; | |
| }, | |
| constcreateGridFallbackTranslator=()=>{ | |
| return(key: string,options?: Record<string,unknown>)=>{ | |
| letvalue=GRID_DEFAULT_TRANSLATIONS[key]||key; | |
| if(options){ | |
| for(const[k,v]ofObject.entries(options)){ | |
| value=value.replace(`{{${k}}}`,String(v)); | |
| } | |
| } | |
| returnvalue; | |
| }; | |
| }; | |
| try{ | |
| constresult=useObjectTranslation(); | |
| consttestValue=result.t('grid.actions'); | |
| if(testValue==='grid.actions'){ | |
| return{ | |
| t: createGridFallbackTranslator(), | |
| }; | |
| } | |
| return{t: result.t}; | |
| }catch{ | |
| return{ | |
| t: createGridFallbackTranslator(), |
| // Default English fallback translations for the grid | ||
| const GRID_DEFAULT_TRANSLATIONS: Record<string, string> = { | ||
| 'grid.actions': 'Actions', | ||
| 'grid.edit': 'Edit', | ||
| 'grid.delete': 'Delete', | ||
| 'grid.export': 'Export', | ||
| 'grid.exportAs': 'Export as {{format}}', | ||
| 'grid.loading': 'Loading grid...', | ||
| 'grid.errorLoading': 'Error loading grid', | ||
| 'grid.pullToRefresh': 'Pull to refresh', | ||
| 'grid.refreshing': 'Refreshing…', | ||
| 'grid.openRecord': 'Open record', | ||
| }; | ||
| /** | ||
| * Safe wrapper for useObjectTranslation that falls back to English defaults | ||
| * when I18nProvider is not available (e.g., standalone usage). | ||
| */ | ||
| function useGridTranslation() { | ||
| try { | ||
| const result = useObjectTranslation(); | ||
| const testValue = result.t('grid.actions'); | ||
| if (testValue === 'grid.actions') { | ||
| return { | ||
| t: (key: string, options?: Record<string, unknown>) => { | ||
| let value = GRID_DEFAULT_TRANSLATIONS[key] || key; | ||
| if (options) { | ||
| for (const [k, v] of Object.entries(options)) { | ||
| value = value.replace(`{{${k}}}`, String(v)); | ||
| } | ||
| } | ||
| return value; | ||
| }, | ||
| }; | ||
| } | ||
| return { t: result.t }; | ||
| } catch { | ||
| return { | ||
| t: (key: string, options?: Record<string, unknown>) => { | ||
| let value = GRID_DEFAULT_TRANSLATIONS[key] || key; | ||
| if (options) { | ||
| for (const [k, v] of Object.entries(options)) { | ||
| value = value.replace(`{{${k}}}`, String(v)); | ||
| } | ||
| } | ||
| return value; | ||
| }, | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
There are extensive packages/plugin-grid/src/__tests__ suites, but the new translation fallback behavior isn’t covered. A targeted test that renders ObjectGrid without an I18nProvider (asserts fallback English) and with an I18nProvider (asserts translated strings) would help ensure i18n regressions don’t slip back in.
| <Button | ||
| key={`action-${action.type}-${idx}`} | ||
| type="button" | ||
| variant="ghost" | ||
| size="icon-sm" | ||
| onClick={() => onViewAction?.(action.type, currentView!)} | ||
| title={DEFAULT_VIEW_ACTION_LABELS[action.type] || action.type} | ||
| > | ||
| {ActionIcon ? <ActionIcon className="h-3.5 w-3.5" /> : null} | ||
| </Button> |
There was a problem hiding this comment.
The new per-view action buttons are icon-only but don’t provide an accessible name (a title tooltip isn’t consistently announced by screen readers). Add an aria-label and/or a visually-hidden label (e.g., sr-only) for each action so the controls are usable with assistive tech.
| const viewActionButtons = schema.viewActions && schema.viewActions.length > 0 ? ( | ||
| <div className="flex items-center gap-1"> | ||
| {schema.viewActions.map((action, idx) => { | ||
| const ActionIcon = action.icon | ||
| ? resolveIcon(action.icon) || DEFAULT_VIEW_ACTION_ICONS[action.type] | ||
| : DEFAULT_VIEW_ACTION_ICONS[action.type]; | ||
| return ( | ||
| <Button | ||
| key={`action-${action.type}-${idx}`} | ||
| type="button" | ||
| variant="ghost" | ||
| size="icon-sm" | ||
| onClick={() => onViewAction?.(action.type, currentView!)} | ||
| title={DEFAULT_VIEW_ACTION_LABELS[action.type] || action.type} | ||
| > | ||
| {ActionIcon ? <ActionIcon className="h-3.5 w-3.5" /> : null} | ||
| </Button> | ||
| ); | ||
| })} | ||
| </div> | ||
| ) : null; |
There was a problem hiding this comment.
onClick={() => onViewAction?.(action.type, currentView!)} uses a non-null assertion even though currentView can be undefined when schema.views is empty (and the buttons still render if viewActions is set). Guard against missing currentView (hide/disable buttons) and avoid passing undefined to a callback typed as ViewType.
| constviewActionButtons=schema.viewActions&&schema.viewActions.length>0 ? ( | |
| <divclassName="flex items-center gap-1"> | |
| {schema.viewActions.map((action,idx)=>{ | |
| constActionIcon=action.icon | |
| ? resolveIcon(action.icon)||DEFAULT_VIEW_ACTION_ICONS[action.type] | |
| : DEFAULT_VIEW_ACTION_ICONS[action.type]; | |
| return( | |
| <Button | |
| key={`action-${action.type}-${idx}`} | |
| type="button" | |
| variant="ghost" | |
| size="icon-sm" | |
| onClick={()=>onViewAction?.(action.type,currentView!)} | |
| title={DEFAULT_VIEW_ACTION_LABELS[action.type]||action.type} | |
| > | |
| {ActionIcon ? <ActionIconclassName="h-3.5 w-3.5"/> : null} | |
| </Button> | |
| ); | |
| })} | |
| </div> | |
| ) : null; | |
| constviewActionButtons= | |
| schema.viewActions&&schema.viewActions.length>0&¤tView | |
| ? ( | |
| <divclassName="flex items-center gap-1"> | |
| {schema.viewActions.map((action,idx)=>{ | |
| constActionIcon=action.icon | |
| ? resolveIcon(action.icon)||DEFAULT_VIEW_ACTION_ICONS[action.type] | |
| : DEFAULT_VIEW_ACTION_ICONS[action.type]; | |
| return( | |
| <Button | |
| key={`action-${action.type}-${idx}`} | |
| type="button" | |
| variant="ghost" | |
| size="icon-sm" | |
| onClick={()=>{ | |
| if(!currentView)return; | |
| onViewAction?.(action.type,currentView); | |
| }} | |
| title={DEFAULT_VIEW_ACTION_LABELS[action.type]||action.type} | |
| > | |
| {ActionIcon ? <ActionIconclassName="h-3.5 w-3.5"/> : null} | |
| </Button> | |
| ); | |
| })} | |
| </div> | |
| ) | |
| : null; |
Console hardcodes UI features (search, breadcrumb siblings, create-view buttons, per-view actions) that aren't representable in schema types, blocking non-Console consumers from configuring them via JSON. Multiple components have hardcoded English text, causing mixed-language UI when locale ≠ en.
HeaderBarSchema — search/actions slots (B1)
Added
crumbs,search,actions,rightContenttoHeaderBarSchema+ Zod. Renderer now supports an inline search input with keyboard shortcut badge, schema-driven action slots, and custom right content.BreadcrumbItem — sibling dropdown navigation (B3)
Added
siblingsfield. Renderer shows aDropdownMenuwithChevronDownwhen siblings are present, enabling quick-switch between peer objects.SidebarNav — badges, collapsible groups, search (B2)
NavItemgainsbadge,badgeVariant,children(nested items)NavGrouptype for grouped navigationsearchEnabled/searchPlaceholderprops with client-side filterCollapsible+SidebarMenuSubViewSwitcher — create view & per-view actions (B5)
Added
allowCreateViewandviewActionstoViewSwitcherSchema+ Zod. Component renders a+button and per-view action icon buttons (share/settings/duplicate/delete) withonCreateView,onViewActioncallback props.Data-table pagination i18n (B4/B6 — partial)
Replaced hardcoded
"Rows per page:"and"Page X of Y (N total)"withuseTableTranslation()safe wrapper (same pattern as ListView'suseListViewTranslation). Addedtable.pageInfoandtable.totalRecordskeys to all 10 locale files.ListView toolbar i18n (B4 — complete)
Replaced all hardcoded English toolbar button labels (Filter, Group, Sort, Export, Color, Search) and popover headers (Filter Records, Group By, Sort Records, Row Color, Color by field, Clear, None, Export as) with
t()calls viauseListViewTranslation(). Added 17 newlist.*i18n keys to all 10 locale files with proper translations.ObjectGrid i18n (B6 — complete)
Added
useGridTranslation()safe wrapper to ObjectGrid. Replaced all hardcoded English strings (Error loading grid, Loading grid..., Actions, Export, Export as, Pull to refresh, Refreshing) witht()calls using existinggrid.*locale keys. Combined with the data-table pagination fix, all grid footer/pagination text now resolves from a single i18n context.Original prompt
This section details on the original issue you should resolve
<issue_title>Platform UI optimization:schema expressiveness gaps, and i18n inconsistencies</issue_title>
<issue_description>## Overview
A comprehensive UI audit of the platform engine (using the CRM Project Task page as reference) revealed two categories of platform-level issues:
The CRM is only an example app; all issues identified are platform engine bugs or missing schema capabilities that affect any consumer.
Part B: Non-Grid UI — Schema Expressiveness Gaps (6 issues)
B1. [P0] HeaderBar schema lacks search/actions slots
HeaderBarSchemaonly supports breadcrumbs. Console hardcodes search (⌘K), notifications, theme toggle, presence avatars inAppHeader.tsx— none of this is schema-configurable.Fix: Extend
HeaderBarSchema:Update
header-barrenderer inpackages/components/src/renderers/navigation/header-bar.tsxto render these new slots.Files:
packages/types/src/navigation.ts,packages/components/src/renderers/navigation/header-bar.tsxB2. [P1] SidebarNav missing badge, collapsible groups, search
SidebarNav(packages/layout/src/SidebarNav.tsx) only supports flatNavItem[]:But
NavigationRendereralready supports badges, collapsible groups, pinning, search, reorder.SidebarNavshould expose a subset of these capabilities:Files:
packages/layout/src/SidebarNav.tsxB3. [P1] Breadcrumb items don't support sibling dropdown navigation
Screenshot shows
Project Task ▾with a dropdown arrow for quick-switching between objects. Console implements this inAppHeader.tsxwithobjectSiblings, butBreadcrumbItemtype has nosiblingsfield:The
header-barrenderer should render aDropdownMenuwhensiblingsis provided.Files:
packages/types/src/navigation.ts,packages/components/src/renderers/navigation/header-bar.tsxB4. [P1] Toolbar i18n: Mixed Chinese/English labels
The page shows
+ 新建(Chinese create button) alongside English toolbar buttons (Filter,Group,Sort). The footer shows7 条记录(Chinese) next toPage 1 of 1 (7 total)(English).Root Cause: Different UI regions use different i18n namespaces or fallback differently. The ListView toolbar uses
useListViewTranslation(), but the create button and page header come from a different namespace.Fix:
addRecordbutton label must go throught('list.addRecord')/t('list.create')recordCounttext must use the same locale asRows per pageFiles:
packages/plugin-list/src/ListView.tsx,packages/i18n/src/B5. [P2] ViewSwitcher schema: No
allowCreateViewor per-view actionsScreenshot shows a
+button at the end of view tabs and[S](share) /[⚙](settings) icons per tab. These are not representable inViewSwitcherSchema.Fix:
Files:
packages/types/src/views.ts,packages/plugin-view/src/ViewSwitcher.tsxB6. [P3] Pagination footer: Mixed-language record count
7 条记录(Chinese) andPage 1 of 1 (7 total)(English) appear simultaneously. All pagination text should resolve from one i18n context.Files:
packages/plugin-grid/src/ObjectGrid.tsx,packages/i18n/src/Core Insight
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.