Skip to content

Commit 8a3451c

Browse files
os-zhuangclaude
andauthored
polish(app-showcase): i18n labels, My Work layout, nav IA, history via feed (#2063)
Round of surface polish on the enterprise pages, all browser-verified. i18n field labels — account / invoice / preference fields were rendering as uppercased machine names (WEBSITE / TAX_ID / THEME …) on Account 360 and Settings because the translation bundle only covered task/project. Added en + zh-CN labels for all three objects; now 网站 / 税号 / 主题 etc. resolve. My Work layout — • KPI tiles now sit in a horizontal row via a `flex` container (the obvious `grid` type is aliased to ObjectGrid by plugin-grid, so it 404s with "object name required" — flex is the correct layout primitive here); • sidebar uses plain element:text lines for Shortcuts (region containers don't render nested page:card bodies — direct components are reliable); • dropped the role-gated card: page-component `visible` expressions get no `current_user` in the renderer's eval context, so true per-role gating belongs in object/field permissions + sharing, not a card expression. History — `record:history` reads an audit store that trackHistory doesn't populate, so its tab was always "No history yet". Removed the redundant tab; field changes already surface in the synthesized discussion feed (verified live: "Industry: Retail → Finance"). Kept trackHistory on account.status + industry since that is what feeds the feed entries. Nav IA — new "Workspace" group (My Work / Approvals / New Project / Settings) at the top, separated from the demo "Pages" gallery. typecheck + 20 showcase tests pass. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a3514bb commit 8a3451c

5 files changed

Lines changed: 128 additions & 27 deletions

File tree

examples/app-showcase/src/apps/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ export const ShowcaseApp = App.create({
1414
branding: { primaryColor: '#7C3AED' },
1515

1616
navigation: [
17+
{
18+
id: 'grp_workspace',
19+
type: 'group',
20+
label: 'Workspace',
21+
icon: 'briefcase',
22+
children: [
23+
{ id: 'nav_my_work', type: 'page', pageName: 'showcase_my_work', label: 'My Work', icon: 'home' },
24+
{ id: 'nav_review_queue', type: 'page', pageName: 'showcase_review_queue', label: 'Approvals', icon: 'check-check' },
25+
{ id: 'nav_new_project_wizard', type: 'page', pageName: 'showcase_new_project_wizard', label: 'New Project (Wizard)', icon: 'wand-2' },
26+
{ id: 'nav_settings', type: 'object', objectName: 'showcase_preference', label: 'Settings', icon: 'settings' },
27+
],
28+
},
1729
{
1830
id: 'grp_data',
1931
type: 'group',
@@ -50,10 +62,6 @@ export const ShowcaseApp = App.create({
5062
label: 'Pages',
5163
icon: 'layout',
5264
children: [
53-
{ id: 'nav_my_work', type: 'page', pageName: 'showcase_my_work', label: 'My Work', icon: 'home' },
54-
{ id: 'nav_review_queue', type: 'page', pageName: 'showcase_review_queue', label: 'Approvals', icon: 'check-check' },
55-
{ id: 'nav_new_project_wizard', type: 'page', pageName: 'showcase_new_project_wizard', label: 'New Project (Wizard)', icon: 'wand-2' },
56-
{ id: 'nav_settings', type: 'object', objectName: 'showcase_preference', label: 'Settings', icon: 'settings' },
5765
{ id: 'nav_gallery', type: 'page', pageName: 'showcase_component_gallery', label: 'Component Gallery', icon: 'layout-template' },
5866
{ id: 'nav_project_workspace', type: 'page', pageName: 'showcase_project_workspace', label: 'New Project + Tasks', icon: 'folder-plus' },
5967
// ADR-0047 interface mode: same object as nav_tasks, curated surface.

examples/app-showcase/src/objects/account.object.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const Account = ObjectSchema.create({
2323
name: Field.text({ label: 'Account Name', required: true, searchable: true, maxLength: 200 }),
2424
industry: Field.select({
2525
label: 'Industry',
26+
trackHistory: true,
2627
options: [
2728
{ label: 'Technology', value: 'technology', default: true },
2829
{ label: 'Finance', value: 'finance' },
@@ -36,6 +37,7 @@ export const Account = ObjectSchema.create({
3637
status: Field.select({
3738
label: 'Lifecycle',
3839
required: true,
40+
trackHistory: true,
3941
options: [
4042
{ label: 'Prospect', value: 'prospect', default: true, color: '#94A3B8' },
4143
{ label: 'Active', value: 'active', color: '#10B981' },

examples/app-showcase/src/pages/account-detail.page.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import type { Page } from '@objectstack/spec/ui';
99
* • `record:related_list` — the account's Projects and Invoices, each a live
1010
* child list (relationshipField = the `account`
1111
* lookup on the child object).
12-
* • `record:history` — the field-level audit trail.
12+
* • field changes surface in the synthesized discussion feed (trackHistory
13+
* on status/industry) — verified: 'Industry: Retail → Finance'.
1314
* • the synthesized `discussion` slot — a unified activity + comment feed
1415
* (@mentions, reactions): the human collaboration surface, for free.
1516
* • `record:highlights` + `record:details` — the summary strip + sections.
@@ -89,16 +90,6 @@ export const AccountDetailPage: Page = {
8990
},
9091
],
9192
},
92-
{
93-
key: 'history',
94-
label: 'History',
95-
children: [
96-
{
97-
type: 'record:history',
98-
properties: { limit: 50, showUser: true, showTimestamp: true },
99-
},
100-
],
101-
},
10293
],
10394
},
10495
},

examples/app-showcase/src/pages/my-work.page.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import type { Page } from '@objectstack/spec/ui';
88
* • a KPI hero row of live `object-metric` tiles (team throughput);
99
* • a personal work queue — `object-grid` filtered to the signed-in user
1010
* via the `{current_user_id}` token (records I own);
11-
* • a role-gated card (`visible` expression) that only the admin sees —
12-
* demonstrating per-user differentiated rendering.
11+
* • a sidebar Shortcuts card pointing at the key surfaces.
12+
*
13+
* (Note: page-component `visible` expressions don't receive a `current_user`
14+
* context in the renderer, so true per-role gating belongs in object/field
15+
* permissions + sharing rules, not a card-level expression.)
1316
*/
1417
export const MyWorkPage: Page = {
1518
name: 'showcase_my_work',
@@ -30,10 +33,21 @@ export const MyWorkPage: Page = {
3033
name: 'main',
3134
width: 'large',
3235
components: [
33-
// KPI hero row — live aggregates over the team's work.
34-
{ type: 'object-metric', properties: { objectName: 'showcase_task', label: 'Open Tasks', icon: 'list-checks', aggregate: { field: 'id', function: 'count' }, filter: { status: { $ne: 'done' } } } },
35-
{ type: 'object-metric', properties: { objectName: 'showcase_task', label: 'In Review', icon: 'eye', aggregate: { field: 'id', function: 'count' }, filter: { status: 'in_review' } } },
36-
{ type: 'object-metric', properties: { objectName: 'showcase_project', label: 'At-Risk Projects', icon: 'alert-triangle', aggregate: { field: 'id', function: 'count' }, filter: { health: 'red' } } },
36+
// KPI hero ROW — three live tiles laid out horizontally via a grid.
37+
{
38+
type: 'flex',
39+
properties: {
40+
direction: 'row',
41+
wrap: true,
42+
gap: 4,
43+
align: 'stretch',
44+
children: [
45+
{ type: 'object-metric', properties: { objectName: 'showcase_task', label: 'Open Tasks', icon: 'list-checks', aggregate: { field: 'id', function: 'count' }, filter: { status: { $ne: 'done' } } } },
46+
{ type: 'object-metric', properties: { objectName: 'showcase_task', label: 'In Review', icon: 'eye', aggregate: { field: 'id', function: 'count' }, filter: { status: 'in_review' } } },
47+
{ type: 'object-metric', properties: { objectName: 'showcase_project', label: 'At-Risk Projects', icon: 'alert-triangle', aggregate: { field: 'id', function: 'count' }, filter: { health: 'red' } } },
48+
],
49+
},
50+
},
3751
{ type: 'element:divider', properties: {} },
3852
// Personal work queue — records owned by the signed-in user.
3953
{
@@ -50,12 +64,12 @@ export const MyWorkPage: Page = {
5064
name: 'sidebar',
5165
width: 'small',
5266
components: [
53-
// Role-gated: only the admin sees this card — differentiated rendering.
54-
{
55-
type: 'page:card',
56-
properties: { title: 'Leadership View', visible: "current_user.email == 'admin@objectos.ai'" },
57-
},
58-
{ type: 'element:text', properties: { content: 'Tip: open Delivery Operations for the org-wide dashboard.' } },
67+
// Sidebar shortcuts — plain text lines (region containers don't render
68+
// nested page:card bodies; direct components are the reliable path).
69+
{ type: 'element:text', properties: { content: 'Shortcuts' } },
70+
{ type: 'element:text', properties: { content: '• Delivery Operations — the org-wide KPI dashboard.' } },
71+
{ type: 'element:text', properties: { content: '• Approvals — items in review awaiting a decision.' } },
72+
{ type: 'element:text', properties: { content: '• New Project (Wizard) — create a project step by step.' } },
5973
],
6074
},
6175
],

examples/app-showcase/src/translations/index.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,49 @@ export const ShowcaseTranslationBundle = {
4545
cover: { label: 'Cover' },
4646
},
4747
},
48+
showcase_account: {
49+
label: 'Account',
50+
pluralLabel: 'Accounts',
51+
fields: {
52+
name: { label: 'Account Name' },
53+
industry: { label: 'Industry' },
54+
annual_revenue: { label: 'Annual Revenue' },
55+
website: { label: 'Website' },
56+
hq: { label: 'Headquarters' },
57+
status: { label: 'Lifecycle' },
58+
tax_id: { label: 'Tax ID' },
59+
billing_email: { label: 'Billing Email' },
60+
support_config: { label: 'Support Config' },
61+
churn_reason: { label: 'Churn Reason' },
62+
},
63+
},
64+
showcase_invoice: {
65+
label: 'Invoice',
66+
pluralLabel: 'Invoices',
67+
fields: {
68+
name: { label: 'Invoice Number' },
69+
account: { label: 'Account' },
70+
owner: { label: 'Owner' },
71+
status: { label: 'Status' },
72+
issued_on: { label: 'Issued On' },
73+
tax_rate: { label: 'Tax Rate (%)' },
74+
paid_on: { label: 'Paid On' },
75+
total: { label: 'Total' },
76+
},
77+
},
78+
showcase_preference: {
79+
label: 'Setting',
80+
pluralLabel: 'Settings',
81+
fields: {
82+
name: { label: 'Name' },
83+
theme: { label: 'Theme' },
84+
default_landing: { label: 'Default Landing Page' },
85+
email_digest: { label: 'Email Digest' },
86+
items_per_page: { label: 'Rows per Page' },
87+
notifications_enabled: { label: 'Enable Notifications' },
88+
compact_density: { label: 'Compact Density' },
89+
},
90+
},
4891
},
4992
},
5093
'zh-CN': {
@@ -83,6 +126,49 @@ export const ShowcaseTranslationBundle = {
83126
cover: { label: '封面' },
84127
},
85128
},
129+
showcase_account: {
130+
label: '客户',
131+
pluralLabel: '客户',
132+
fields: {
133+
name: { label: '客户名称' },
134+
industry: { label: '行业' },
135+
annual_revenue: { label: '年收入' },
136+
website: { label: '网站' },
137+
hq: { label: '总部' },
138+
status: { label: '生命周期' },
139+
tax_id: { label: '税号' },
140+
billing_email: { label: '账单邮箱' },
141+
support_config: { label: '支持配置' },
142+
churn_reason: { label: '流失原因' },
143+
},
144+
},
145+
showcase_invoice: {
146+
label: '发票',
147+
pluralLabel: '发票',
148+
fields: {
149+
name: { label: '发票号' },
150+
account: { label: '客户' },
151+
owner: { label: '负责人' },
152+
status: { label: '状态' },
153+
issued_on: { label: '开具日期' },
154+
tax_rate: { label: '税率 (%)' },
155+
paid_on: { label: '付款日期' },
156+
total: { label: '合计' },
157+
},
158+
},
159+
showcase_preference: {
160+
label: '设置',
161+
pluralLabel: '设置',
162+
fields: {
163+
name: { label: '名称' },
164+
theme: { label: '主题' },
165+
default_landing: { label: '默认着陆页' },
166+
email_digest: { label: '邮件摘要' },
167+
items_per_page: { label: '每页行数' },
168+
notifications_enabled: { label: '启用通知' },
169+
compact_density: { label: '紧凑密度' },
170+
},
171+
},
86172
},
87173
},
88174
};

0 commit comments

Comments
 (0)