Skip to content

Commit aa52e51

Browse files
Copilothotlong
andcommitted
feat(ui): add Airtable Interface parity schemas — UserActions, Appearance, Tabs, AddRecord, InterfacePageConfig
- Add VisualizationTypeSchema for switchable view type whitelist - Add UserActionsConfigSchema for sort/search/filter/rowHeight/addRecordForm/buttons toggles - Add AppearanceConfigSchema for showDescription and allowedVisualizations - Add ViewTabSchema for multi-tab view interface with order/icon/pinned/isDefault - Add AddRecordConfigSchema for add record entry point (position/mode/formView) - Add showRecordCount and allowPrinting to ListViewSchema - Add InterfacePageConfigSchema for page-level Airtable Interface config - Add interfaceConfig field to PageSchema - Add 31 new tests covering all new schemas - Update ROADMAP.md with Phase E and updated metrics Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 23e16e6 commit aa52e51

5 files changed

Lines changed: 599 additions & 2 deletions

File tree

ROADMAP.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ This strategy ensures rapid iteration while maintaining a clear path to producti
9898
| Service Contracts | 25 |
9999
| Contracts Implemented | 13 (52%) |
100100
| Test Files | 199 |
101-
| Tests Passing | 5,468 / 5,468 |
101+
| Tests Passing | 5,805 / 5,805 |
102102
| `@deprecated` Items | 3 |
103103
| Protocol Domains | 15 (Data, UI, AI, API, Automation, Cloud, Contracts, Identity, Integration, Kernel, QA, Security, Shared, Studio, System) |
104104

@@ -175,7 +175,7 @@ business/custom objects, aligning with industry best practices (e.g., ServiceNow
175175

176176
- [x] **Data Protocol** — Object, Field (35+ types), Query, Filter, Validation, Hook, Datasource, Dataset, Analytics, Document, Storage Name Mapping (`tableName`/`columnName`), Feed & Activity Timeline (FeedItem, Comment, Mention, Reaction, FieldChange), Record Subscription (notification channels)
177177
- [x] **Driver Specifications** — Memory, PostgreSQL, MongoDB driver schemas + SQL/NoSQL abstractions
178-
- [x] **UI Protocol** — View (List/Form/Kanban/Calendar/Gantt), App, Dashboard, Report, Action, Page (16 types), Chart, Widget, Theme, Animation, DnD, Touch, Keyboard, Responsive, Offline, Notification, i18n, Content Elements, Enhanced Activity Timeline (`RecordActivityProps` unified timeline, `RecordChatterProps` sidebar/drawer), Unified Navigation Protocol (`NavigationItem` as single source of truth with 7 types: object/dashboard/page/url/report/action/group; `NavigationArea` for business domain partitioning; `order`/`badge`/`requiredPermissions` on all nav items)
178+
- [x] **UI Protocol** — View (List/Form/Kanban/Calendar/Gantt), App, Dashboard, Report, Action, Page (16 types), Chart, Widget, Theme, Animation, DnD, Touch, Keyboard, Responsive, Offline, Notification, i18n, Content Elements, Enhanced Activity Timeline (`RecordActivityProps` unified timeline, `RecordChatterProps` sidebar/drawer), Unified Navigation Protocol (`NavigationItem` as single source of truth with 7 types: object/dashboard/page/url/report/action/group; `NavigationArea` for business domain partitioning; `order`/`badge`/`requiredPermissions` on all nav items), Airtable Interface Parity (`UserActionsConfig`, `AppearanceConfig`, `ViewTab`, `AddRecordConfig`, `InterfacePageConfig`, `showRecordCount`, `allowPrinting`)
179179
- [x] **System Protocol** — Manifest, Auth Config, Cache, Logging, Metrics, Tracing, Audit, Encryption, Masking, Migration, Tenant, Translation, Search Engine, HTTP Server, Worker, Job, Object Storage, Notification, Message Queue, Registry Config, Collaboration, Compliance, Change Management, Disaster Recovery, License, Security Context, Core Services, SystemObjectName/SystemFieldName Constants, StorageNameMapping Utilities
180180
- [x] **Automation Protocol** — Flow (autolaunched/screen/schedule), Workflow, State Machine, Trigger Registry, Approval, ETL, Sync, Webhook
181181
- [x] **AI Protocol** — Agent, Agent Action, Conversation, Cost, MCP, Model Registry, NLQ, Orchestration, Predictive, RAG Pipeline, Runtime Ops, Feedback Loop, DevOps Agent, Plugin Development
@@ -465,6 +465,20 @@ business/custom objects, aligning with industry best practices (e.g., ServiceNow
465465
- [ ] Real-time collaborative page editing
466466
- [ ] Page analytics — page views, element interactions, user engagement
467467

468+
#### Phase E: Interface Parity — User Actions, Appearance & Tabs (v3.x) ✅
469+
470+
> Aligns Spec UI configuration with Airtable Interface capabilities.
471+
472+
- [x] `UserActionsConfigSchema` — Declarative toggles for sort/search/filter/rowHeight/addRecordForm/buttons in view toolbar (`src/ui/view.zod.ts`)
473+
- [x] `AppearanceConfigSchema` — showDescription toggle and allowedVisualizations whitelist (`src/ui/view.zod.ts`)
474+
- [x] `VisualizationTypeSchema` — Enum of switchable visualization types (grid/kanban/gallery/calendar/timeline/gantt/map) (`src/ui/view.zod.ts`)
475+
- [x] `ViewTabSchema` — Multi-tab view interface with order, icon, pinned, isDefault, visible (`src/ui/view.zod.ts`)
476+
- [x] `AddRecordConfigSchema` — Add record entry point with position/mode/formView (`src/ui/view.zod.ts`)
477+
- [x] `showRecordCount` — Boolean on `ListViewSchema` for record count display (`src/ui/view.zod.ts`)
478+
- [x] `allowPrinting` — Boolean on `ListViewSchema` for print capability (`src/ui/view.zod.ts`)
479+
- [x] `InterfacePageConfigSchema` — Page-level interface configuration (source, levels, filterBy, appearance, userFilters, userActions, addRecord, showRecordCount, allowPrinting) (`src/ui/page.zod.ts`)
480+
- [x] `PageSchema.interfaceConfig` — Optional interface config on pages for Airtable-style declarative page setup (`src/ui/page.zod.ts`)
481+
468482
### 8.2 Dashboard Enhancement — Airtable Dashboard Parity
469483

470484
> See [Airtable Dashboard Gap Analysis](content/docs/guides/airtable-dashboard-analysis.mdx) for the full evaluation and implementation examples.

packages/spec/src/ui/page.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import {
88
ElementDataSourceSchema,
99
BlankPageLayoutSchema,
1010
PageVariableSchema,
11+
InterfacePageConfigSchema,
1112
type Page,
1213
type PageComponent,
1314
type PageRegion,
1415
type ElementDataSource,
1516
type RecordReviewConfig,
17+
type InterfacePageConfig,
1618
} from './page.zod';
1719

1820
describe('PageComponentSchema', () => {
@@ -948,3 +950,148 @@ describe('Page end-to-end', () => {
948950
expect(page.object).toBe('order');
949951
});
950952
});
953+
954+
// ---------------------------------------------------------------------------
955+
// InterfacePageConfigSchema — Airtable Interface parity
956+
// ---------------------------------------------------------------------------
957+
describe('InterfacePageConfigSchema', () => {
958+
it('should accept empty config', () => {
959+
const config: InterfacePageConfig = InterfacePageConfigSchema.parse({});
960+
expect(config).toBeDefined();
961+
});
962+
963+
it('should accept full interface page config', () => {
964+
const config = InterfacePageConfigSchema.parse({
965+
source: 'customers',
966+
levels: 1,
967+
filterBy: [{ field: 'status', operator: 'equals', value: 'active' }],
968+
appearance: {
969+
showDescription: true,
970+
allowedVisualizations: ['grid', 'gallery', 'kanban'],
971+
},
972+
userFilters: {
973+
elements: ['grid', 'gallery', 'kanban'],
974+
tabs: [
975+
{ name: 'my_customers', label: 'my customers', isDefault: true },
976+
{ name: 'all_records', label: 'All records' },
977+
],
978+
},
979+
userActions: {
980+
sort: true,
981+
search: true,
982+
filter: true,
983+
rowHeight: true,
984+
addRecordForm: false,
985+
buttons: [],
986+
},
987+
addRecord: {
988+
enabled: true,
989+
position: 'bottom',
990+
mode: 'inline',
991+
},
992+
showRecordCount: true,
993+
allowPrinting: true,
994+
});
995+
996+
expect(config.source).toBe('customers');
997+
expect(config.levels).toBe(1);
998+
expect(config.appearance?.allowedVisualizations).toHaveLength(3);
999+
expect(config.userFilters?.tabs).toHaveLength(2);
1000+
expect(config.userActions?.sort).toBe(true);
1001+
expect(config.showRecordCount).toBe(true);
1002+
expect(config.allowPrinting).toBe(true);
1003+
});
1004+
1005+
it('should accept config with only source and levels', () => {
1006+
const config = InterfacePageConfigSchema.parse({
1007+
source: 'orders',
1008+
levels: 2,
1009+
});
1010+
expect(config.source).toBe('orders');
1011+
expect(config.levels).toBe(2);
1012+
});
1013+
1014+
it('should reject levels < 1', () => {
1015+
expect(() => InterfacePageConfigSchema.parse({
1016+
levels: 0,
1017+
})).toThrow();
1018+
});
1019+
});
1020+
1021+
// ---------------------------------------------------------------------------
1022+
// PageSchema with interfaceConfig
1023+
// ---------------------------------------------------------------------------
1024+
describe('PageSchema with interfaceConfig', () => {
1025+
it('should accept page with interfaceConfig', () => {
1026+
const page = PageSchema.parse({
1027+
name: 'customer_list_page',
1028+
label: '客户列表页面',
1029+
description: '浏览并筛选所有客户信息',
1030+
type: 'list',
1031+
object: 'customers',
1032+
interfaceConfig: {
1033+
source: 'customers',
1034+
levels: 1,
1035+
filterBy: [],
1036+
appearance: {
1037+
showDescription: true,
1038+
allowedVisualizations: ['grid', 'gallery', 'kanban'],
1039+
},
1040+
userFilters: {
1041+
elements: ['grid', 'gallery', 'kanban'],
1042+
tabs: [
1043+
{ name: 'my_customers', label: 'my customers', isDefault: true, pinned: true },
1044+
{ name: 'all_records', label: 'All records' },
1045+
],
1046+
},
1047+
userActions: {
1048+
sort: true,
1049+
search: true,
1050+
filter: true,
1051+
rowHeight: true,
1052+
addRecordForm: false,
1053+
},
1054+
addRecord: {
1055+
enabled: true,
1056+
position: 'bottom',
1057+
mode: 'inline',
1058+
},
1059+
showRecordCount: true,
1060+
allowPrinting: true,
1061+
},
1062+
regions: [],
1063+
});
1064+
1065+
expect(page.interfaceConfig?.source).toBe('customers');
1066+
expect(page.interfaceConfig?.appearance?.allowedVisualizations).toHaveLength(3);
1067+
expect(page.interfaceConfig?.userActions?.sort).toBe(true);
1068+
expect(page.interfaceConfig?.showRecordCount).toBe(true);
1069+
expect(page.interfaceConfig?.allowPrinting).toBe(true);
1070+
});
1071+
1072+
it('should accept page without interfaceConfig (backward compatibility)', () => {
1073+
const page = PageSchema.parse({
1074+
name: 'test_page',
1075+
label: 'Test Page',
1076+
regions: [],
1077+
});
1078+
expect(page.interfaceConfig).toBeUndefined();
1079+
});
1080+
1081+
it('should accept dashboard page with interfaceConfig', () => {
1082+
const page = PageSchema.parse({
1083+
name: 'sales_dashboard',
1084+
label: 'Sales Dashboard',
1085+
type: 'dashboard',
1086+
interfaceConfig: {
1087+
appearance: {
1088+
showDescription: false,
1089+
},
1090+
allowPrinting: false,
1091+
},
1092+
regions: [],
1093+
});
1094+
expect(page.interfaceConfig?.appearance?.showDescription).toBe(false);
1095+
expect(page.interfaceConfig?.allowPrinting).toBe(false);
1096+
});
1097+
});

packages/spec/src/ui/page.zod.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod';
55
import { SortItemSchema } from '../shared/enums.zod';
66
import { I18nLabelSchema, AriaPropsSchema } from './i18n.zod';
77
import { ResponsiveConfigSchema } from './responsive.zod';
8+
import {
9+
UserActionsConfigSchema,
10+
AppearanceConfigSchema,
11+
ViewTabSchema,
12+
AddRecordConfigSchema,
13+
} from './view.zod';
814

915
/**
1016
* Page Region Schema
@@ -194,6 +200,43 @@ export const RecordReviewConfigSchema = z.object({
194200
.describe('Show review progress indicator'),
195201
});
196202

203+
/**
204+
* Interface Page Configuration Schema (Airtable Interface parity)
205+
* Page-level declarative configuration for Airtable-style interface pages.
206+
* Covers title/data binding, levels, filter by, appearance, user actions,
207+
* tabs, record count, add record, and advanced options (printing).
208+
*
209+
* @see Airtable Interface → right panel (Page / Data / Appearance / User filters / User actions / Advanced)
210+
*/
211+
export const InterfacePageConfigSchema = z.object({
212+
/** Data binding */
213+
source: z.string().optional().describe('Source object name for the page'),
214+
levels: z.number().int().min(1).optional().describe('Number of hierarchy levels to display'),
215+
filterBy: z.array(z.unknown()).optional().describe('Page-level filter criteria'),
216+
217+
/** Appearance */
218+
appearance: AppearanceConfigSchema.optional().describe('Appearance and visualization configuration'),
219+
220+
/** User filters */
221+
userFilters: z.object({
222+
elements: z.array(z.enum(['grid', 'gallery', 'kanban'])).optional()
223+
.describe('Visualization element types available in user filter bar'),
224+
tabs: z.array(ViewTabSchema).optional().describe('User-configurable tabs'),
225+
}).optional().describe('User filter configuration'),
226+
227+
/** User actions */
228+
userActions: UserActionsConfigSchema.optional().describe('User action toggles'),
229+
230+
/** Add record */
231+
addRecord: AddRecordConfigSchema.optional().describe('Add record entry point configuration'),
232+
233+
/** Record count */
234+
showRecordCount: z.boolean().optional().describe('Show record count at page bottom'),
235+
236+
/** Advanced */
237+
allowPrinting: z.boolean().optional().describe('Allow users to print the page'),
238+
}).describe('Interface-level page configuration (Airtable parity)');
239+
197240
/**
198241
* Page Schema
199242
* Defines a composition of components for a specific context.
@@ -249,6 +292,10 @@ export const PageSchema = z.object({
249292
isDefault: z.boolean().default(false),
250293
assignedProfiles: z.array(z.string()).optional(),
251294

295+
/** Interface Page Configuration (Airtable Interface parity) */
296+
interfaceConfig: InterfacePageConfigSchema.optional()
297+
.describe('Interface-level page configuration (for Airtable-style interface pages)'),
298+
252299
/** ARIA accessibility attributes */
253300
aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes'),
254301
});
@@ -262,3 +309,4 @@ export type ElementDataSource = z.infer<typeof ElementDataSourceSchema>;
262309
export type RecordReviewConfig = z.infer<typeof RecordReviewConfigSchema>;
263310
export type BlankPageLayoutItem = z.infer<typeof BlankPageLayoutItemSchema>;
264311
export type BlankPageLayout = z.infer<typeof BlankPageLayoutSchema>;
312+
export type InterfacePageConfig = z.infer<typeof InterfacePageConfigSchema>;

0 commit comments

Comments
 (0)