diff --git a/examples/app-showcase/src/pages/account-cockpit.page.ts b/examples/app-showcase/src/pages/account-cockpit.page.ts index 523b4432cd..dd7d7d2fcb 100644 --- a/examples/app-showcase/src/pages/account-cockpit.page.ts +++ b/examples/app-showcase/src/pages/account-cockpit.page.ts @@ -5,11 +5,11 @@ import { definePage } from '@objectstack/spec/ui'; /** * Account Cockpit — a `kind:'react'` business scenario (ADR-0081). * - * A customer-360 cockpit: a live-search account list (React text input drives - * the real ``'s `filters`), a selected account edited in a real - * ``, and a related-data strip that aggregates the account's - * projects and invoices via cross-object `useAdapter` queries. Demonstrates - * live search, master-detail edit, and cross-object roll-ups together. + * Customer-360 over `showcase_account`: a live search filters a real + * ``, selecting an account loads it into an `` editor and + * rolls up related projects & invoices via `useAdapter()` cross-object queries. + * + * Styling (ADR-0065): no Tailwind — inline `style={{}}` with `hsl(var(--token))`. */ export const AccountCockpitPage = definePage({ name: 'showcase_account_cockpit', @@ -38,49 +38,49 @@ function Page() { }, [adapter, sel, reload]); const filters = q.trim() ? ['name', 'contains', q.trim()] : undefined; + const card = { background: 'hsl(var(--card))', border: '1px solid hsl(var(--border))', borderRadius: 'var(--radius)' }; const Stat = ({ label, value, accent }) => ( -
-
{label}
-
{value}
+
+
{label}
+
{value}
); return ( -
-
+
+
-

Account Cockpit

-

Customer-360 over showcase_account — search, edit, and roll up related projects & invoices.

+

Account Cockpit

+

Customer-360 over showcase_account — search, edit, and roll up related projects & invoices.

- { setQ(e.target.value); setSel(null); }} - placeholder="Search accounts…" - className="w-64 rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-indigo-500 focus:outline-none" /> + { setQ(e.target.value); setSel(null); }} placeholder="Search accounts…" + style={{ width: 256, borderRadius: 'var(--radius)', border: '1px solid hsl(var(--border))', background: 'hsl(var(--background))', color: 'hsl(var(--foreground))', padding: '8px 12px', fontSize: 14, outline: 'none' }} />
-
-
+
+
setSel(r)} />
-
+
{sel ? ( -
+
- +
-
+
{ setSel(null); setReload((k) => k + 1); }} onCancel={() => setSel(null)} />
) : ( -
-
🛰️
-

Search and select an account.

+
+
🛰️
+

Search and select an account.

)}
diff --git a/examples/app-showcase/src/pages/crm-workbench.page.ts b/examples/app-showcase/src/pages/crm-workbench.page.ts index d79ed4c7db..bfeea1906c 100644 --- a/examples/app-showcase/src/pages/crm-workbench.page.ts +++ b/examples/app-showcase/src/pages/crm-workbench.page.ts @@ -5,27 +5,15 @@ import { definePage } from '@objectstack/spec/ui'; /** * CRM Workbench — a `kind:'react'` page (ADR-0081, the TRUSTED tier). * - * The whole page is REAL React executed at render by `@object-ui/react-runtime` - * (hooks, event handlers, arbitrary JS) — NOT the constrained parse-never-execute - * `kind:'html'` tier. It demonstrates exactly what a real customer business UI - * needs: a master/detail workbench that COMPOSES the platform's real data - * components — `` (the object table) and `` — with React - * state to wire complex interaction the fixed page schema cannot express: + * Real React executed at render (hooks, handlers, arbitrary JS) composing the + * platform's real data components — `` + `` — into a + * master/detail workbench with a live KPI strip. * - * - click a project row → load that record into the editor (controlled `recordId`) - * - save the form → refresh the list (`onSuccess` bumps a remount key) - * - "New project" → the same editor in create mode - * - a live KPI strip → `useAdapter()` queries the object directly - * - * The injected scope exposes `React`, `useAdapter`, and the curated public data - * blocks as PascalCase components (`ListView`, `ObjectForm`, `ObjectMetric`, …), - * each a real registered renderer. Layout is plain HTML + Tailwind. - * - * NOTE: `kind:'react'` executes author code, so it is gated by the host - * capability `CAP_REACT_PAGES`, which defaults ON (the platform trusts its - * reviewed, draft-gated authors). A deployment that does not trust its authors - * turns it off server-side with `OS_PAGE_REACT=off`, in which case - * this page renders a "disabled on this deployment" notice instead of executing. + * Styling (ADR-0065): page source is runtime metadata, so the console's + * build-time Tailwind never scans it — utility classes silently no-op. So the + * page uses ZERO Tailwind: layout/chrome is inline `style={{}}` with + * `hsl(var(--token))` theme colors (real CSS, theme-aware), and the data + * components bring their own compiled styling. */ export const CrmWorkbenchPage = definePage({ name: 'showcase_crm_workbench', @@ -48,64 +36,50 @@ function Page() { setStats({ total: rows.length, active: rows.filter((r) => r.status === 'active').length }); } catch (e) { /* ignore in demo */ } }, [adapter]); - React.useEffect(() => { refreshStats(); }, [refreshStats, reloadKey]); const openNew = () => { setSelected(null); setMode('create'); }; const onRowClick = (rec) => { setSelected(rec); setMode('edit'); }; const afterSave = () => { setSelected(null); setMode('edit'); setReloadKey((k) => k + 1); }; - const editing = mode === 'create' || selected; + const card = { background: 'hsl(var(--card))', border: '1px solid hsl(var(--border))', borderRadius: 'var(--radius)', padding: 16 }; + const eyebrow = { fontSize: 12, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em', color: 'hsl(var(--muted-foreground))' }; + const big = { marginTop: 4, fontSize: 30, fontWeight: 700, color: 'hsl(var(--foreground))' }; + return ( -
-
+
+
-

CRM Workbench

-

Master/detail over showcase_project — real <ListView> + <ObjectForm> wired with React state.

+

CRM Workbench

+

Master/detail over showcase_project — real <ListView> + <ObjectForm> wired with React state.

- +
-
-
-
Total projects
-
{stats.total}
-
-
-
Active
-
{stats.active}
-
-
-
Editing
-
{mode === 'create' ? 'New project' : selected ? (selected.name || selected.id) : '—'}
-
+
+
Total projects
{stats.total}
+
Active
{stats.active}
+
Editing
{mode === 'create' ? 'New project' : selected ? (selected.name || selected.id) : '—'}
-
-
- +
+ + navigation={{ mode: 'none' }} onRowClick={onRowClick} />
-
+
{editing ? ( { setSelected(null); }} - /> + onSuccess={afterSave} onCancel={() => { setSelected(null); }} /> ) : ( -
-
🗂️
-

Select a project to edit, or create a new one.

+
+
🗂️
+

Select a project to edit, or create a new one.

)}
diff --git a/examples/app-showcase/src/pages/inquiry-triage.page.ts b/examples/app-showcase/src/pages/inquiry-triage.page.ts index 315be1bbcc..d21218d93c 100644 --- a/examples/app-showcase/src/pages/inquiry-triage.page.ts +++ b/examples/app-showcase/src/pages/inquiry-triage.page.ts @@ -5,12 +5,12 @@ import { definePage } from '@objectstack/spec/ui'; /** * Inquiry Triage Inbox — a `kind:'react'` business scenario (ADR-0081). * - * Models the classic support/lead-triage queue every CRM needs: status TABS - * with live counts, a real `` filtered by the active tab, and a detail - * panel with one-click status actions ("Mark Contacted" / "Close") that persist - * via `useAdapter().update` and refresh the list + counts. Demonstrates - * React-state-driven server filtering, cross-row aggregation, and a status - * workflow — none of which the fixed page schema can express. + * A support/lead-triage queue: status TABS with live counts filter a real + * ``, and a detail panel with one-click status actions persists via + * `useAdapter().update` and refreshes the list + counts. + * + * Styling (ADR-0065): no Tailwind — page chrome is inline `style={{}}` with + * `hsl(var(--token))` theme colors; the data component brings its own styling. */ export const InquiryTriagePage = definePage({ name: 'showcase_inquiry_triage', @@ -47,53 +47,60 @@ function Page() { const TABS = [['all', 'All'], ['new', 'New'], ['contacted', 'Contacted'], ['closed', 'Closed']]; const filters = tab === 'all' ? undefined : ['status', '=', tab]; - const STATUS_COLOR = { new: 'bg-blue-100 text-blue-700', contacted: 'bg-amber-100 text-amber-700', closed: 'bg-emerald-100 text-emerald-700' }; + const STATUS_COLOR = { new: 'hsl(217 91% 60%)', contacted: 'hsl(38 92% 50%)', closed: 'hsl(142 70% 45%)' }; + + const card = { background: 'hsl(var(--card))', border: '1px solid hsl(var(--border))', borderRadius: 'var(--radius)' }; + const tabBtn = (active) => ({ + display: 'flex', alignItems: 'center', gap: 8, borderRadius: 9999, padding: '6px 16px', fontSize: 14, fontWeight: 600, + border: '1px solid ' + (active ? 'transparent' : 'hsl(var(--border))'), cursor: 'pointer', + background: active ? 'hsl(var(--primary))' : 'transparent', + color: active ? 'hsl(var(--primary-foreground))' : 'hsl(var(--muted-foreground))', + }); return ( -
+
-

Inquiry Triage

-

A support queue over showcase_inquiry — tabs filter a real <ListView>; one click moves an inquiry's status.

+

Inquiry Triage

+

A support queue over showcase_inquiry — tabs filter a real <ListView>; one click moves an inquiry's status.

-
+
{TABS.map(([k, label]) => ( - ))}
-
-
+
+
setSel(r)} />
-
+
{sel ? ( -
+
-
-

{sel.name}

- {sel.status} +
+

{sel.name}

+ {sel.status}
-

{sel.company} · {sel.email}

+

{sel.company} · {sel.email}

-

{sel.message || 'No message.'}

-
+

{sel.message || 'No message.'}

+
+ style={{ borderRadius: 'var(--radius)', background: 'hsl(38 92% 50%)', color: '#fff', padding: '8px 16px', fontSize: 14, fontWeight: 600, border: 'none', cursor: 'pointer', opacity: sel.status === 'contacted' ? 0.4 : 1 }}>Mark Contacted + style={{ borderRadius: 'var(--radius)', background: 'hsl(142 70% 40%)', color: '#fff', padding: '8px 16px', fontSize: 14, fontWeight: 600, border: 'none', cursor: 'pointer', opacity: sel.status === 'closed' ? 0.4 : 1 }}>Close
) : ( -
-
📥
-

Select an inquiry to triage.

+
+
📥
+

Select an inquiry to triage.

)}
diff --git a/examples/app-showcase/src/pages/invoice-console.page.ts b/examples/app-showcase/src/pages/invoice-console.page.ts index 29a2594d03..4f32faf0a7 100644 --- a/examples/app-showcase/src/pages/invoice-console.page.ts +++ b/examples/app-showcase/src/pages/invoice-console.page.ts @@ -5,11 +5,11 @@ import { definePage } from '@objectstack/spec/ui'; /** * Invoice Console — a `kind:'react'` business scenario (ADR-0081). * - * Accounts-receivable management: a KPI strip aggregating invoices by status - * (useAdapter), a status segmented-filter driving a real ``, a real - * `` for create + edit, and a "Mark Paid" quick action on the - * selected invoice. Demonstrates aggregation KPIs, segmented filtering, full - * CRUD, and a one-click status transition in one screen. + * Accounts-receivable workbench over `showcase_invoice`: aggregate KPIs, a + * status segmented control filtering a real ``, an `` + * editor, and a one-click "Mark Paid" collect action. + * + * Styling (ADR-0065): no Tailwind — inline `style={{}}` with `hsl(var(--token))`. */ export const InvoiceConsolePage = definePage({ name: 'showcase_invoice_console', @@ -48,44 +48,52 @@ function Page() { const FILTERS = [['all', 'All'], ['draft', 'Draft'], ['sent', 'Sent'], ['paid', 'Paid'], ['void', 'Void']]; const filters = status === 'all' ? undefined : ['status', '=', status]; const editing = mode === 'create' || sel; + + const card = { background: 'hsl(var(--card))', border: '1px solid hsl(var(--border))', borderRadius: 'var(--radius)' }; + const eyebrow = { fontSize: 12, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em', color: 'hsl(var(--muted-foreground))' }; const Kpi = ({ label, value, accent }) => ( -
-
{label}
-
{value}
+
+
{label}
+
{value}
); + const pill = (active) => ({ + borderRadius: 9999, padding: '4px 14px', fontSize: 14, fontWeight: 600, cursor: 'pointer', + border: '1px solid ' + (active ? 'transparent' : 'hsl(var(--border))'), + background: active ? 'hsl(var(--primary))' : 'transparent', + color: active ? 'hsl(var(--primary-foreground))' : 'hsl(var(--muted-foreground))', + }); return ( -
-
+
+
-

Invoice Console

-

Accounts receivable over showcase_invoice — aggregate, filter, edit, and collect.

+

Invoice Console

+

Accounts receivable over showcase_invoice — aggregate, filter, edit, and collect.

- +
-
+
- - - + + +
-
+
{FILTERS.map(([k, label]) => ( - + ))}
-
-
+
+
{ setSel(r); setMode('edit'); }} />
-
+
{editing ? ( setSel(null)} /> {mode === 'edit' && sel && sel.status !== 'paid' ? ( - + ) : null} ) : ( -
-
🧾
-

Select an invoice, or create one.

+
+
🧾
+

Select an invoice, or create one.

)}
diff --git a/examples/app-showcase/src/pages/renewals-pipeline.page.ts b/examples/app-showcase/src/pages/renewals-pipeline.page.ts index f7d2770bb2..7ad96726d3 100644 --- a/examples/app-showcase/src/pages/renewals-pipeline.page.ts +++ b/examples/app-showcase/src/pages/renewals-pipeline.page.ts @@ -5,19 +5,14 @@ import { definePage } from '@objectstack/spec/ui'; /** * Renewals Pipeline — a `kind:'react'` business scenario (ADR-0081). * - * Authored as a DOGFOOD of the react-tier component contract - * (skills/objectstack-ui/references/react-blocks.md): every block prop below is - * taken straight from the contract — no guessing. A renewals manager works a list - * of accounts on the left; selecting one drives a 360° panel on the right - * (highlights + the account's invoices + a value-by-status chart) and a slide-out - * to update the account in place. + * A renewals manager works a list of accounts by lifecycle stage; selecting one + * drives a 360° panel (highlights + invoices + a value-by-status chart) and a + * pre-styled `` to update the account in place. + * Every block prop is taken straight from the react-tier contract + * (skills/objectstack-ui/references/react-blocks.md). * - * Blocks exercised, with the contract props each accepts: - * objectName(req) · viewType · filters · columns · searchableFields · navigation · onRowClick - * objectName · recordId · fields · layout - * objectName · recordId · relationshipField · columns · limit · showViewAll · title - * objectName(req) · aggregate · title · showLegend - * objectName(req) · mode · recordId · formType · drawerSide · drawerWidth · onSuccess · onCancel + * Styling (ADR-0065): no Tailwind — inline `style={{}}` with `hsl(var(--token))`; + * data blocks and the drawer bring their own compiled styling. */ export const RenewalsPipelinePage = definePage({ name: 'showcase_renewals_pipeline', @@ -36,26 +31,23 @@ function Page() { { id: 'at_risk', label: 'At risk' }, { id: 'churned', label: 'Churned' }, ]; + const stageBtn = (active) => ({ + borderRadius: 'var(--radius)', padding: '6px 12px', fontSize: 14, cursor: 'pointer', + border: '1px solid ' + (active ? 'hsl(var(--primary))' : 'hsl(var(--border))'), + background: active ? 'hsl(var(--primary) / 0.1)' : 'transparent', + color: 'hsl(var(--foreground))', fontWeight: active ? 600 : 400, + }); return ( -
-
+
+
-

Renewals Pipeline

-

Work the renewal book by lifecycle stage.

+

Renewals Pipeline

+

Work the renewal book by lifecycle stage.

-
+
{STAGES.map((s) => ( - + ))}
-
+
{!sel ? ( -
+
Select an account to see its renewal picture.
) : ( - <> -
-

Account 360

- + +
+

Account 360

+
- + - + - + {editing ? ( - { if (!o) setEditing(false); }} onSuccess={() => { setEditing(false); setReload((n) => n + 1); }} - onCancel={() => setEditing(false)} - /> + onCancel={() => setEditing(false)} /> ) : null} - +
)}
); -} -`, +}`, }); diff --git a/examples/app-showcase/src/pages/task-desk.page.ts b/examples/app-showcase/src/pages/task-desk.page.ts index f57407878d..e46e151ba5 100644 --- a/examples/app-showcase/src/pages/task-desk.page.ts +++ b/examples/app-showcase/src/pages/task-desk.page.ts @@ -4,11 +4,15 @@ import { definePage } from '@objectstack/spec/ui'; /** * Task Desk — a `kind:'react'` business scenario (ADR-0081) showing the - * popup-edit patterns real apps need: a **slide-out drawer** to edit a row, and - * a **centered modal** to create. Both are author-built React overlays (fixed - * positioning + backdrop + Esc/close state) that wrap the platform's real - * `` — demonstrating that the react tier composes drawer/modal - * interactions the in-page master/detail layout can't. + * popup-edit patterns real apps need: a **slide-out drawer** to edit a row and a + * **centered modal** to create — both rendered by the platform's own + * `` variant, NOT hand-rolled. + * + * Styling note (ADR-0065): page source is runtime metadata, so the console's + * build-time Tailwind never scans it — arbitrary utility classes silently no-op. + * So this page uses ZERO Tailwind: the overlay/backdrop/animation come from the + * pre-styled ObjectForm drawer/modal, and the thin page chrome uses inline + * `style={{}}` with `hsl(var(--token))` theme colors (real CSS, theme-aware). */ export const TaskDeskPage = definePage({ name: 'showcase_task_desk', @@ -17,67 +21,44 @@ export const TaskDeskPage = definePage({ kind: 'react', source: ` function Page() { - const adapter = useAdapter(); - const [editId, setEditId] = React.useState(null); // drawer (edit existing) + const [editId, setEditId] = React.useState(null); // drawer (edit existing) const [creating, setCreating] = React.useState(false); // modal (create new) const [reload, setReload] = React.useState(0); - - const closeAll = () => { setEditId(null); setCreating(false); }; - const afterWrite = () => { closeAll(); setReload((k) => k + 1); }; - - // Close overlays on Escape — the kind of detail a real app needs. - React.useEffect(() => { - const onKey = (e) => { if (e.key === 'Escape') closeAll(); }; - window.addEventListener('keydown', onKey); - return () => window.removeEventListener('keydown', onKey); - }, []); + const afterWrite = () => { setEditId(null); setCreating(false); setReload((k) => k + 1); }; return ( -
-
+
+
-

Task Desk

-

Click a task to edit in a drawer; create one in a modal — both wrap the real <ObjectForm>.

+

Task Desk

+

+ Click a task to edit it in a drawer; create one in a modal — both render the platform's pre-styled <ObjectForm> overlay. +

- +
-
- setEditId(r.id)} /> -
+ setEditId(r.id)} /> - {/* Drawer — edit an existing task */} - {editId ? ( -
-
-
-
-

Edit task

- -
-
- -
-
-
+ {/* Drawer — edit an existing task. Backdrop, Esc, slide animation are the component's. */} + {editId != null ? ( + { if (!o) setEditId(null); }} + onSuccess={afterWrite} /> ) : null} - {/* Modal — create a new task */} + {/* Modal — create a new task. */} {creating ? ( -
-
-
-
-

New task

- -
- -
-
+ { if (!o) setCreating(false); }} + onSuccess={afterWrite} /> ) : null}
);