From ab3740a833aa7a5514d778f1c9cff259c3cb598a Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:15:27 +0800 Subject: [PATCH] docs(showcase): dogfood the react-tier authoring loop + golden Renewals page (ADR-0081) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-to-end proof that the react-tier loop closes: a kind:'react' page authored straight from the generated component contract (react-blocks.md) passes `os validate`, and the same page with a wrong prop is caught. - examples/app-showcase: add renewals-pipeline.page.ts — a Renewals Pipeline scenario exercising five server-connected blocks (ListView, RecordHighlights, ObjectChart, RecordRelatedList, ObjectForm-as-drawer), every prop taken from the contract. Wired into the stack; validates clean; typechecks. - docs/audits/2026-06-react-tier-authoring-dogfood.md: captures the run — clean page → "Validation passed" (exit 0); injecting a missing required objectName on + an onSucces typo on → prop gate flags the error (exit 1) and the typo warning. Demonstrates generated contract → author reads it → validate enforces it compose into a working loop. Co-Authored-By: Claude Opus 4.8 --- .../2026-06-react-tier-authoring-dogfood.md | 55 ++++++++ examples/app-showcase/objectstack.config.ts | 4 +- examples/app-showcase/src/pages/index.ts | 1 + .../src/pages/renewals-pipeline.page.ts | 132 ++++++++++++++++++ 4 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 docs/audits/2026-06-react-tier-authoring-dogfood.md create mode 100644 examples/app-showcase/src/pages/renewals-pipeline.page.ts diff --git a/docs/audits/2026-06-react-tier-authoring-dogfood.md b/docs/audits/2026-06-react-tier-authoring-dogfood.md new file mode 100644 index 0000000000..29e8145d43 --- /dev/null +++ b/docs/audits/2026-06-react-tier-authoring-dogfood.md @@ -0,0 +1,55 @@ +# React-tier authoring dogfood (ADR-0081) + +Goal: prove the loop the react-tier was built for actually closes — **an author +(human or AI) writes a `kind:'react'` page knowing every component's props from +the contract, and `os validate` catches it when they don't.** Not "the gate has +unit tests" — an end-to-end run through the real CLI on a real app. + +## The loop + +1. **Contract** — `skills/objectstack-ui/references/react-blocks.md` lists every + injected block (``, ``, ``, ``, + ``, ``, …) and the exact props each accepts, + tagged `data` / `binding` / `controlled` / `callback`. It is **generated** from + the spec schemas (`packages/spec/src/ui/react-blocks.ts`), so it can't drift + into fiction. +2. **Author** — `examples/app-showcase/src/pages/renewals-pipeline.page.ts` was + written straight from that contract (no guessing): a renewals manager works a + `` of accounts, and selecting one drives `` + + `` + `` and a slide-out ``. + Five server-connected blocks, every prop taken from the contract. +3. **Gate** — `os validate` step 3d (`validateReactPageProps`, ADR-0081 Phase 2) + parses each react page's real JSX and checks block usage against the contract. + +## Evidence + +**Authored-correctly → passes.** With the page wired into the showcase stack: + +``` +→ Checking React-source page props (ADR-0081)... +✓ Validation passed (98ms) # exit 0 +``` + +**Authored-wrong → caught.** Injecting two realistic mistakes — dropping the +required `objectName` binding on ``, and a `onSucces` typo of the +`onSuccess` callback on ``: + +``` +⚠ page "showcase_renewals_pipeline" › : has prop "onSucces" — did you mean "onSuccess"? +✗ React-source page prop check failed (1 issue) + • page "showcase_renewals_pipeline" › : is missing the required prop "objectName". + rule: react-prop-missing-required at pages[29].source + # exit 1 +``` + +The missing required binding is an **error** (fails the build); the near-miss prop +name is a **warning** (likely typo, surfaced but non-fatal — the contract's data +props are a curated subset so arbitrary unknown props aren't flagged, keeping +false positives near zero). + +## Conclusion + +The three pieces — **generated contract**, **author reads it**, **validate enforces +it** — compose into a working loop. An AI handed `react-blocks.md` writes correct +props, and a wrong prop is caught at `os validate` time before it ever renders. +`renewals-pipeline.page.ts` stays in the showcase as the golden, validated example. diff --git a/examples/app-showcase/objectstack.config.ts b/examples/app-showcase/objectstack.config.ts index 431b36238a..f1ea0032e9 100644 --- a/examples/app-showcase/objectstack.config.ts +++ b/examples/app-showcase/objectstack.config.ts @@ -22,7 +22,7 @@ import { ChartGalleryDashboard, OpsDashboard } from './src/dashboards/index.js'; import { ShowcaseTaskDataset, ShowcaseProjectDataset } from './src/datasets/index.js'; import { allReports } from './src/reports/index.js'; import { allActions } from './src/actions/index.js'; -import { ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, TaskDeskPage, PageVariablesPage, ContactFormPage } from './src/pages/index.js'; +import { ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, TaskDeskPage, PageVariablesPage, ContactFormPage, RenewalsPipelinePage } from './src/pages/index.js'; import { allFlows } from './src/flows/index.js'; import { allWebhooks } from './src/webhooks/index.js'; import { allHooks } from './src/hooks/index.js'; @@ -154,7 +154,7 @@ export default defineStack({ apps: [ShowcaseApp], portals: allPortals, views: [TaskViews, ProjectViews, InquiryViews, BusinessUnitViews], - pages: [ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, TaskDeskPage, PageVariablesPage, ContactFormPage], + pages: [ComponentGalleryPage, ProjectWorkspacePage, ProjectDetailPage, TaskWorkbenchPage, TaskTriagePage, TaskBoardPage, TaskCalendarPage, TaskGalleryPage, TaskSchedulePage, TaskTimelinePage, TaskMapPage, TaskAllViewsPage, ActiveProjectsPage, TaskDetailPage, AccountDetailPage, ReviewQueuePage, NewProjectWizardPage, MyWorkPage, SettingsPage, StylingGalleryPage, CommandCenterPage, CommandCenterJsxPage, CrmWorkbenchPage, InquiryTriagePage, AccountCockpitPage, InvoiceConsolePage, TaskDeskPage, PageVariablesPage, ContactFormPage, RenewalsPipelinePage], dashboards: [ChartGalleryDashboard, OpsDashboard], books: allBooks, datasets: [ShowcaseTaskDataset, ShowcaseProjectDataset], diff --git a/examples/app-showcase/src/pages/index.ts b/examples/app-showcase/src/pages/index.ts index cf0fd72a5f..18259d52ab 100644 --- a/examples/app-showcase/src/pages/index.ts +++ b/examples/app-showcase/src/pages/index.ts @@ -23,6 +23,7 @@ export { InvoiceConsolePage } from './invoice-console.page.js'; export { TaskDeskPage } from './task-desk.page.js'; export { PageVariablesPage } from './page-variables.page.js'; export { ContactFormPage } from './contact-form.page.js'; +export { RenewalsPipelinePage } from './renewals-pipeline.page.js'; export { TaskBoardPage, TaskCalendarPage, diff --git a/examples/app-showcase/src/pages/renewals-pipeline.page.ts b/examples/app-showcase/src/pages/renewals-pipeline.page.ts new file mode 100644 index 0000000000..f7d2770bb2 --- /dev/null +++ b/examples/app-showcase/src/pages/renewals-pipeline.page.ts @@ -0,0 +1,132 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +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. + * + * 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 + */ +export const RenewalsPipelinePage = definePage({ + name: 'showcase_renewals_pipeline', + label: 'Renewals Pipeline (React)', + type: 'home', + kind: 'react', + source: ` +function Page() { + const [sel, setSel] = React.useState(null); + const [editing, setEditing] = React.useState(false); + const [reload, setReload] = React.useState(0); + const [stage, setStage] = React.useState('active'); + + const STAGES = [ + { id: 'active', label: 'Active' }, + { id: 'at_risk', label: 'At risk' }, + { id: 'churned', label: 'Churned' }, + ]; + + return ( +
+
+
+

Renewals Pipeline

+

Work the renewal book by lifecycle stage.

+
+
+ {STAGES.map((s) => ( + + ))} +
+ { setSel(record.id); setEditing(false); }} + /> +
+ +
+ {!sel ? ( +
+ Select an account to see its renewal picture. +
+ ) : ( + <> +
+

Account 360

+ +
+ + + + + + + + {editing ? ( + { setEditing(false); setReload((n) => n + 1); }} + onCancel={() => setEditing(false)} + /> + ) : null} + + )} +
+
+ ); +} +`, +});