背景
Airtable 等现代 SaaS 平台广泛采用了右侧 Activity Feed 面板,将字段变更、评论(支持@提及)、系统任务活动等融合为统一时间线,这为协同、审核、历史追踪带来极大便利。
当前 @objectstack/spec 协议已经有
feeds: true / activities: true / trackHistory: true 等能力开关(Data Protocol)- 字段级的审计开关和变更跟踪能力
- UI 层有
record:activity 组件,但 record:chatter 未定义(空壳),也缺乏 Feed/Comment 数据结构
目标
- 对标 Airtable/Notion/Salesforce,补全和标准化 Comment/Feed 协议,支持 audit + comment + mention + reaction + 通知订阅(一致时间线)
- 让 UI 配置模式下能声明式插入"所有活动"侧边栏,兼容过滤、操作、@提及等能力
主要差距和需求
- 缺少 FeedItem/Comment/Mention 数据协议(应统一 comments���field_change、task、note、approval 等为 FeedItemType 并支持 @Mentions、Reactions)
- record:chatter 组件 props 必须补全,支持评论输入/过滤/订阅等配置
- RecordActivity/Chatter 组件应支持过滤(all/changes/comments/... 过滤)、unifiedTimeline 等属性
- Feed 评论支持 @提及(mention)、回复(线程)、reaction、编辑删除、附件
- 补全 API 协议(GET/POST/PUT/DELETE /feed、/reactions、/subscribe 等端点约定)
推荐实现步骤
- 创建
src/data/feed.zod.ts,定义 FeedItemType、FeedItemSchema、MentionSchema、ReactionSchema - 增强
component.zod.ts,扩展 RecordActivityProps/RecordChatterProps 属性,并修正 ComponentPropsMap - 定义
src/data/subscription.zod.ts(通知订阅能力,可选) - 明确 API 端点模型约定
相关参考
任务参考实现(可分多个 PR 完善)
- 定义 feed 协议基础 zod schema
- 增强 UI 层协议与 props
- API 约定与安全
- 完善测试
test
对标 Airtable 截图中的 右侧 Activity 面板(评论 + 字段变更历史 + @ Mentions)
🔍 一、截图分析:Airtable 的 Comments & Activity 功能拆解
从你提供的 Airtable 截图
可以看到:
| 功能区 | Airtable 实现 | 关键能力 |
|---|
| Activity 侧边栏 | 记录详情页右侧固定面板 | 独立区域、可折叠 |
| Activity Feed | "All activity" 下拉过滤 + 时间线 | 字段变更 + 评论混合排列 |
| 字段变更历史 | 显示字段名、旧值→新值、时间戳 | REGION → Asia-Pacific, STATUS → Active |
| 评论 (Comments) | 底部 "Leave a comment" 输入框 | 富文本、@mentions |
| 通知铃铛 | 面板顶部通知开关 | 订阅/取消该记录的变更通知 |
| 操作者信息 | "You edited using Omni 1d ago" | 用户头像、来源、时间 |
| 扩展历史 | "Upgrade to extend your history!" | 付费门控的历史深度 |
🟢 二、当前协议已有的基础设施
A. Data Protocol — 能力开关(✅ 良好)
exportconstObjectCapabilities=z.object({trackHistory: z.boolean().default(false).describe('Enable field history tracking for audit compliance'),feeds: z.boolean().default(false).describe('Enable social feed, comments, and mentions (Chatter-like)'),activities: z.boolean().default(false).describe('Enable standard tasks and events tracking'),// ...});评估:feeds: true 和 activities: true 和 trackHistory: true 这三个开关已经存在,定义了启用的意图,但 没有对应��� Feed/Comment 数据模型协议。
B. Field Protocol — 字段级变更追踪(✅ 存在)
trackFeedHistory: z.boolean().optional().describe('Track field changes in Chatter/activity feed (Salesforce pattern)'),auditTrail: z.boolean().default(false).describe('Enable detailed audit trail for this field'),评估: 字段级别的 trackFeedHistory 可控制哪些字段的变更出现在 Feed 中(类似 Airtable 截图中只显示了 REGION, OPPORTUNITIES, STATUS 等关键字段变更)。
C. UI Protocol — Activity 组件(🟡 骨架存在)
exportconstRecordActivityProps=z.object({types: z.array(z.enum(['task','event','email','call','note'])).optional(),limit: z.number().int().positive().default(10),showCompleted: z.boolean().default(false),aria: AriaPropsSchema.optional(),});以及 ComponentPropsMap 中的注册:
// Record'record:details': RecordDetailsProps,'record:related_list': RecordRelatedListProps,'record:highlights': RecordHighlightsProps,'record:activity': RecordActivityProps,'record:chatter': EmptyProps,// ← 空壳!'record:path': RecordPathProps,
D. Page Protocol — Lead Detail 示例用法(✅ 演示存在)
{type: 'record:activity',id: 'lead_activity',label: 'Activity Timeline',properties: {types: ['task','event','email','call','note'],limit: 20,showCompleted: false,},},// History Tab{type: 'record:related_list',id: 'field_history',label: 'Field History',properties: {objectName: 'field_history',relationshipField: 'record_id',columns: ['field','old_value','new_value','changed_by','changed_date'],},},E. System Protocol — Audit(✅ 丰富)
src/system/audit.zod.ts 有完整的 AuditEventSchema、AuditEventActor、AuditEventChange 等,适合后端存储。
🔴 三、关键差距分析(对标 Airtable)
| # | 差距 | 严重度 | 说明 |
|---|
| G1 | record:chatter 是 EmptyProps | 🔴 Critical | Comments/Mentions 组件没有任何 Props 定义,无法声明式配置 |
| G2 | 缺少 FeedItem / Comment 数据模型 | 🔴 Critical | feeds: true 开关存在,但没有 src/data/feed.zod.ts 定义 Feed Item 的数据结构 |
| G3 | Activity 缺少 comment 类型 | 🟠 Major | RecordActivityProps.types 只有 ['task', 'event', 'email', 'call', 'note'],没有 'comment' 和 'field_change' |
| G4 | 缺少 Activity Filter / Stream 控制 | 🟠 Major | Airtable 有 "All activity" 下拉菜单可筛选(只看评论 / 只看变更),当前没有 filterMode |
| G5 | 缺少通知订阅协议 | 🟠 Major | Airtable 的铃铛图标控制记录级订阅,当前无 RecordSubscription 协议 |
| G6 | 缺少 @mention 协议 | 🟡 Medium | Feeds 描述提到 "mentions" 但无 Mention 的目标类型定义(user / team / record) |
| G7 | History 与 Comment 未统一 Timeline | 🟡 Medium | CRM 示例中 Activity 和 History 放在不同 Tab,Airtable 是混合 Timeline |
| G8 | 缺少 Reaction / Emoji 协议 | 🟢 Nice-to-have | Airtable/Notion 风格的 reaction(👍❤️🎉) |
🛠️ 四、推荐实现方案
Phase 1: Data Protocol — Feed & Comment 数据模型
需要创建 src/data/feed.zod.ts:
// src/data/feed.zod.ts — Feed & Comment Data Protocolimport{z}from'zod';/** * Feed Item Type * Unified activity types for the record timeline */exportconstFeedItemType=z.enum(['comment',// User comment (rich text + mentions)'field_change',// Field value change (audit)'task',// Task created/completed'event',// Calendar event'email',// Email sent/received'call',// Phone call logged'note',// Internal note'file',// File uploaded/deleted'record_create',// Record was created'record_delete',// Record was deleted'approval',// Approval action (submit/approve/reject)'sharing',// Sharing change'system',// System-generated event]);exporttypeFeedItemType=z.infer<typeofFeedItemType>;/** * @mention Target */exportconstMentionSchema=z.object({type: z.enum(['user','team','record']).describe('Mention target type'),id: z.string().describe('Target ID'),name: z.string().describe('Display name for rendering'),offset: z.number().int().min(0).describe('Character offset in body text'),length: z.number().int().min(1).describe('Length of mention token in body text'),});exporttypeMention=z.infer<typeofMentionSchema>;/** * Field Change Entry (for field_change feed items) */exportconstFieldChangeEntrySchema=z.object({field: z.string().describe('Field machine name'),fieldLabel: z.string().optional().describe('Field display label'),oldValue: z.unknown().optional().describe('Previous value'),newValue: z.unknown().optional().describe('New value'),oldDisplayValue: z.string().optional().describe('Human-readable old value'),newDisplayValue: z.string().optional().describe('Human-readable new value'),});exporttypeFieldChangeEntry=z.infer<typeofFieldChangeEntrySchema>;/** * Reaction (Emoji Reaction) */exportconstReactionSchema=z.object({emoji: z.string().describe('Emoji character or shortcode (e.g., "👍", ":thumbsup:")'),userIds: z.array(z.string()).describe('Users who reacted'),count: z.number().int().min(1).describe('Total reaction count'),});exporttypeReaction=z.infer<typeofReactionSchema>;/** * Feed Item Schema * A single entry in the unified activity timeline. * * @example Comment * { * type: 'comment', * body: 'Great progress on this deal! @jane.doe can you follow up?', * mentions: [{ type: 'user', id: 'user_123', name: 'Jane Doe', offset: 36, length: 9 }], * actor: { type: 'user', id: 'user_456', name: 'John Smith' } * } * * @example Field Change * { * type: 'field_change', * changes: [ * { field: 'status', oldDisplayValue: 'New', newDisplayValue: 'Active' }, * { field: 'region', oldDisplayValue: '', newDisplayValue: 'Asia-Pacific' } * ], * actor: { type: 'user', id: 'user_456', name: 'John Smith' } * } */exportconstFeedItemSchema=z.object({/** Unique identifier */id: z.string().describe('Feed item ID'),/** Feed item type */type: FeedItemType.describe('Activity type'),/** Target record reference */object: z.string().describe('Object name (e.g., "account")'),recordId: z.string().describe('Record ID this feed item belongs to'),/** Actor (who performed the action) */actor: z.object({type: z.enum(['user','system','service','automation']).describe('Actor type'),id: z.string().describe('Actor ID'),name: z.string().optional().describe('Actor display name'),avatarUrl: z.string().url().optional().describe('Actor avatar URL'),source: z.string().optional().describe('Source application (e.g., "Omni", "API", "Studio")'),}).describe('Who performed this action'),/** Content (for comments/notes) */body: z.string().optional().describe('Rich text body (Markdown supported)'),/** @Mentions */mentions: z.array(MentionSchema).optional().describe('Mentioned users/teams/records'),/** Field changes (for field_change type) */changes: z.array(FieldChangeEntrySchema).optional().describe('Field-level changes'),/** Reactions */reactions: z.array(ReactionSchema).optional().describe('Emoji reactions on this item'),/** Reply threading */parentId: z.string().optional().describe('Parent feed item ID for threaded replies'),replyCount: z.number().int().min(0).default(0).describe('Number of replies'),/** Visibility */visibility: z.enum(['public','internal','private']).default('public').describe('Visibility: public (all users), internal (team only), private (author + mentioned)'),/** Timestamps */createdAt: z.string().datetime().describe('Creation timestamp'),updatedAt: z.string().datetime().optional().describe('Last edit timestamp'),editedAt: z.string().datetime().optional().describe('When comment was last edited'),isEdited: z.boolean().default(false).describe('Whether comment has been edited'),});exporttypeFeedItem=z.infer<typeofFeedItemSchema>;Phase 2: UI Protocol — 增强 record:chatter 和 record:activity
// Enhanced RecordActivityProps — Unified TimelineexportconstRecordActivityProps=z.object({/** Activity types to display (unified enum) */types: z.array(FeedItemType).optional().describe('Feed item types to show (default: all)'),/** Default filter mode (Airtable-style dropdown) */filterMode: z.enum(['all','comments_only','changes_only','tasks_only']).default('all').describe('Default activity filter'),/** Allow user to switch filter modes */showFilterToggle: z.boolean().default(true).describe('Show filter dropdown in panel header'),/** Pagination */limit: z.number().int().positive().default(20).describe('Number of items to load per page'),/** Show completed activities */showCompleted: z.boolean().default(false),/** Merge field_change + comment in a unified timeline */unifiedTimeline: z.boolean().default(true).describe('Mix field changes and comments in one timeline (Airtable style)'),/** Show the comment input box at the bottom */showCommentInput: z.boolean().default(true).describe('Show "Leave a comment" input at the bottom'),/** Enable @mentions in comments */enableMentions: z.boolean().default(true),/** Enable emoji reactions */enableReactions: z.boolean().default(false),/** Enable threaded replies */enableThreading: z.boolean().default(false),/** Show notification subscription toggle (bell icon) */showSubscriptionToggle: z.boolean().default(true).describe('Show bell icon for record-level notification subscription'),/** ARIA accessibility */aria: AriaPropsSchema.optional(),});// New: RecordChatterProps (replaces EmptyProps)exportconstRecordChatterProps=z.object({/** Panel position */position: z.enum(['sidebar','inline','drawer']).default('sidebar').describe('Where to render the chatter panel'),/** Panel width (for sidebar/drawer) */width: z.union([z.string(),z.number()]).optional().describe('Panel width (e.g., "350px", "30%")'),/** Collapsible */collapsible: z.boolean().default(true),defaultCollapsed: z.boolean().default(false),/** Feed configuration (delegates to RecordActivityProps) */feed: RecordActivityProps.optional(),/** ARIA accessibility */aria: AriaPropsSchema.optional(),});Phase 3: API Protocol — Feed 端点
// API Contract for Feed operations// Would go in src/api/ or src/contracts/// GET /api/data/{object}/{recordId}/feed?type=all&limit=20&cursor=xxx// POST /api/data/{object}/{recordId}/feed { type: 'comment', body: '...' }// PUT /api/data/{object}/{recordId}/feed/{feedId} { body: '...' }// DELETE /api/data/{object}/{recordId}/feed/{feedId}// POST /api/data/{object}/{recordId}/feed/{feedId}/reactions { emoji: '👍' }// POST /api/data/{object}/{recordId}/subscribe { events: ['comment', 'field_change'] }// DELETE /api/data/{object}/{recordId}/subscribePhase 4: Record Subscription (通知)
// src/data/subscription.zod.ts — Record-Level Notification Subscriptionimport{z}from'zod';exportconstRecordSubscriptionSchema=z.object({/** Target */object: z.string().describe('Object name'),recordId: z.string().describe('Record ID'),/** Subscriber */userId: z.string().describe('Subscribing user ID'),/** Events to subscribe to */events: z.array(z.enum(['comment','mention','field_change','task','approval','all',])).default(['all']).describe('Event types to receive notifications for'),/** Notification channels */channels: z.array(z.enum(['in_app','email','push','slack'])).default(['in_app']).describe('Notification delivery channels'),/** Active */active: z.boolean().default(true),createdAt: z.string().datetime(),});exporttypeRecordSubscription=z.infer<typeofRecordSubscriptionSchema>;
📊 五、总结评分
| 维度 | 当前评分 | 实施后目标 | 说明 |
|---|
| Data Model (Feed/Comment) | 🔴 D | ⭐ A | 缺少 FeedItemSchema,开关有但无数据结构 |
| UI Component (Chatter) | 🟡 C | ⭐ A | record:chatter 空壳,record:activity 缺少 unified timeline |
| API (Feed CRUD) | 🔴 D | ⭐ A | apiMethods 有 history 但无 feed 端点协议 |
| Field History Tracking | 🟢 B+ | ⭐ A | trackFeedHistory + auditTrail 字段级已定义 |
| Notification/Subscription | 🔴 D | ⭐ A | 无记录级订阅协议 |
| @Mention | 🔴 D | ⭐ A | feeds 描述提及 mentions 但无数据结构 |
📋 实施优先级
- P0 —
src/data/feed.zod.ts (FeedItem + Comment + FieldChange + Mention 数据模型) - P0 — 增强
RecordActivityProps (unifiedTimeline, filterMode, commentInput) - P0 — 实现
RecordChatterProps 替换 EmptyProps - P1 —
src/data/subscription.zod.ts (记录级通知订阅) - P1 — API Contract for Feed CRUD
- P2 — Reactions、Threading、Rich Media Attachments
需要我开 Issue 或直接创建 PR 来实现这些协议变更吗?
背景
Airtable 等现代 SaaS 平台广泛采用了右侧 Activity Feed 面板,将字段变更、评论(支持@提及)、系统任务活动等融合为统一时间线,这为协同、审核、历史追踪带来极大便利。
当前 @objectstack/spec 协议已经有
feeds: true/activities: true/trackHistory: true等能力开关(Data Protocol)record:activity组件,但record:chatter未定义(空壳),也缺乏 Feed/Comment 数据结构目标
主要差距和需求
推荐实现步骤
src/data/feed.zod.ts,定义 FeedItemType、FeedItemSchema、MentionSchema、ReactionSchemacomponent.zod.ts,扩展RecordActivityProps/RecordChatterProps属性,并修正 ComponentPropsMapsrc/data/subscription.zod.ts(通知订阅能力,可选)相关参考
任务参考实现(可分多个 PR 完善)
test
🔍 一、截图分析:Airtable 的 Comments & Activity 功能拆解
从你提供的 Airtable 截图
可以看到:
REGION → Asia-Pacific,STATUS → Active🟢 二、当前协议已有的基础设施
A. Data Protocol — 能力开关(✅ 良好)
评估:
feeds: true和activities: true和trackHistory: true这三个开关已经存在,定义了启用的意图,但 没有对应��� Feed/Comment 数据模型协议。B. Field Protocol — 字段级变更追踪(✅ 存在)
评估: 字段级别的
trackFeedHistory可控制哪些字段的变更出现在 Feed 中(类似 Airtable 截图中只显示了 REGION, OPPORTUNITIES, STATUS 等关键字段变更)。C. UI Protocol — Activity 组件(🟡 骨架存在)
以及 ComponentPropsMap 中的注册:
D. Page Protocol — Lead Detail 示例用法(✅ 演示存在)
E. System Protocol — Audit(✅ 丰富)
src/system/audit.zod.ts有完整的AuditEventSchema、AuditEventActor、AuditEventChange等,适合后端存储。🔴 三、关键差距分析(对标 Airtable)
record:chatter是 EmptyPropsFeedItem/Comment数据模型feeds: true开关存在,但没有src/data/feed.zod.ts定义 Feed Item 的数据结构comment类型RecordActivityProps.types只有['task', 'event', 'email', 'call', 'note'],没有'comment'和'field_change'filterModeRecordSubscription协议@mention协议🛠️ 四、推荐实现方案
Phase 1: Data Protocol — Feed & Comment 数据模型
需要创建
src/data/feed.zod.ts:Phase 2: UI Protocol — 增强
record:chatter和record:activityPhase 3: API Protocol — Feed 端点
Phase 4: Record Subscription (通知)
📊 五、总结评分
FeedItemSchema,开关有但无数据结构record:chatter空壳,record:activity缺少 unified timelineapiMethods有history但无 feed 端点协议trackFeedHistory+auditTrail字段级已定义feeds描述提及 mentions 但无数据结构📋 实施优先级
src/data/feed.zod.ts(FeedItem + Comment + FieldChange + Mention 数据模型)RecordActivityProps(unifiedTimeline, filterMode, commentInput)RecordChatterProps替换 EmptyPropssrc/data/subscription.zod.ts(记录级通知订阅)