Skip to content

Commit 95cc205

Browse files
os-zhuangclaude
andauthored
feat(app-showcase): re-instate grid KPIs, role-gated note, History tab (#2066)
Now that the SDUI renderer fixes landed in objectui (#1814), restore the showcase features that were dropped as workarounds: My Work — KPI hero row moves from `flex` back to an equal-width `grid` (layout grid no longer shadowed by ObjectGrid); sidebar gains a per-user `visible`-gated note (`user.email == 'admin@objectos.ai'` shows for the admin, a different-email note stays hidden) demonstrating component-level role gating. Account 360 — the History tab returns: `record:history` now self-fetches from sys_activity field_change events (trackHistory on status/industry), so the tab lists real audit entries instead of "No history yet". Browser-verified on :5181: grid-cols KPI row, role-gated visibility, and the History tab populating after an industry change. typecheck + 20 tests pass. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent fa8964d commit 95cc205

2 files changed

Lines changed: 25 additions & 18 deletions

File tree

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +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-
* • field changes surface in the synthesized discussion feed (trackHistory
13-
* on status/industry) — verified: 'Industry: Retail → Finance'.
12+
* • `record:history` — a self-fetching audit tab (sys_activity field_change),
13+
* and the same changes also surface in the discussion feed.
1414
* • the synthesized `discussion` slot — a unified activity + comment feed
1515
* (@mentions, reactions): the human collaboration surface, for free.
1616
* • `record:highlights` + `record:details` — the summary strip + sections.
@@ -90,6 +90,15 @@ export const AccountDetailPage: Page = {
9090
},
9191
],
9292
},
93+
{
94+
key: 'history',
95+
label: 'History',
96+
children: [
97+
// Self-fetches from sys_activity (field_change events) via record
98+
// context — trackHistory on status/industry feeds the entries.
99+
{ type: 'record:history', properties: { limit: 50 } },
100+
],
101+
},
93102
],
94103
},
95104
},

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

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,10 @@ import type { Page } from '@objectstack/spec/ui';
55
/**
66
* My Work — a role-aware workspace home that *composes* live data the way a
77
* real landing page does, instead of listing one component per type:
8-
* • a KPI hero row of live `object-metric` tiles (team throughput);
8+
* • a KPI hero row of live `object-metric` tiles in an equal-width `grid`;
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 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.)
11+
* • sidebar shortcuts + a per-user `visible`-gated note on `user.email`.
1612
*/
1713
export const MyWorkPage: Page = {
1814
name: 'showcase_my_work',
@@ -33,14 +29,12 @@ export const MyWorkPage: Page = {
3329
name: 'main',
3430
width: 'large',
3531
components: [
36-
// KPI hero ROW — three live tiles laid out horizontally via a grid.
32+
// KPI hero ROW — equal-width tiles in a 3-col grid (layout grid, not ObjectGrid).
3733
{
38-
type: 'flex',
34+
type: 'grid',
3935
properties: {
40-
direction: 'row',
41-
wrap: true,
36+
columns: 3,
4237
gap: 4,
43-
align: 'stretch',
4438
children: [
4539
{ type: 'object-metric', properties: { objectName: 'showcase_task', label: 'Open Tasks', icon: 'list-checks', aggregate: { field: 'id', function: 'count' }, filter: { status: { $ne: 'done' } } } },
4640
{ type: 'object-metric', properties: { objectName: 'showcase_task', label: 'In Review', icon: 'eye', aggregate: { field: 'id', function: 'count' }, filter: { status: 'in_review' } } },
@@ -64,12 +58,16 @@ export const MyWorkPage: Page = {
6458
name: 'sidebar',
6559
width: 'small',
6660
components: [
67-
// Sidebar shortcuts — plain text lines (region containers don't render
68-
// nested page:card bodies; direct components are the reliable path).
6961
{ 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.' } },
62+
{ type: 'element:text', properties: { content: '• Delivery Operations — org-wide KPI dashboard.' } },
63+
{ type: 'element:text', properties: { content: '• Approvals — items in review.' } },
64+
{ type: 'element:text', properties: { content: '• New Project (Wizard).' } },
65+
// Per-user rendering via `visible` on the signed-in user (now that the
66+
// renderer feeds `user` into the expression context). The first line
67+
// shows for the admin; the second is gated to a different email and
68+
// stays hidden — proving the gate, not just always-on rendering.
69+
{ type: 'element:text', properties: { visible: "user.email == 'admin@objectos.ai'", content: '✓ Admin-only note (visible: user.email == admin@objectos.ai)' } },
70+
{ type: 'element:text', properties: { visible: "user.email == 'nobody@example.com'", content: 'This must NOT appear (gated to a different user).' } },
7371
],
7472
},
7573
],

0 commit comments

Comments
 (0)