OSS-first docs
These docs teach the open system first: contracts, generated surfaces, runtimes, governance, and incremental adoption. Studio shows up as the operating layer on top, not as the source of truth.
Display Data with DataViews
Define a filterable, sortable transaction history view that works across web and mobile without duplicating UI code.
1. Define the underlying query
First, create a query operation to fetch the data:
import { defineQuery } from '@lssm-tech/lib.contracts-spec';
export const ListTransactions = defineQuery({
meta: {
key: 'billing.listTransactions',
version: '1.0.0',
description: 'Fetch customer transaction history',
},
io: {
input: /* pagination + filters */,
output: /* array of transactions */,
},
policy: { auth: 'user' },
});2. Define the DataView spec
Wrap your query with presentation metadata:
import { defineDataView } from '@lssm-tech/lib.contracts-spec/data-views';
import { ListTransactions } from './list-transactions';
export const TransactionHistory = defineDataView({
meta: {
key: 'billing.transactionHistory',
version: '1.0.0',
entity: 'transaction',
title: 'Transaction History',
description: 'Customer payment history',
domain: 'billing',
owners: ['team-billing'],
tags: ['payments'],
stability: 'stable',
},
source: {
primary: {
key: ListTransactions.meta.key,
version: ListTransactions.meta.version,
},
},
view: {
kind: 'table',
executionMode: 'client',
selection: 'multiple',
columnVisibility: true,
columnResizing: true,
columnPinning: true,
rowExpansion: {
fields: ['notes', 'renewalDate', 'lastActivityAt'],
},
initialState: {
pageSize: 4,
hiddenColumns: ['notes'],
pinnedColumns: {
left: ['account'],
},
sorting: [{ field: 'arr', desc: true }],
},
fields: [
{ key: 'account', label: 'Account', dataPath: 'account', sortable: true },
{ key: 'owner', label: 'Owner', dataPath: 'owner', sortable: true },
{ key: 'status', label: 'Status', dataPath: 'status', sortable: true },
{
key: 'amount',
label: 'Amount',
dataPath: 'amount',
sortable: true,
format: { type: 'currency', currency: 'EUR', rounded: true },
},
{
key: 'renewalDate',
label: 'Renewal',
dataPath: 'renewalDate',
format: { type: 'date', dateStyle: 'medium' },
},
{
key: 'processingTime',
label: 'Processing time',
dataPath: 'processingMinutes',
format: { type: 'duration', unit: 'minute', display: 'digital' },
},
{
key: 'notes',
label: 'Notes',
dataPath: 'notes',
visibility: { minDataDepth: 'detailed' },
},
],
collection: {
viewModes: {
defaultMode: 'table',
allowedModes: ['list', 'grid', 'table'],
},
toolbar: {
search: true,
viewMode: true,
filters: true,
density: true,
dataDepth: true,
},
pagination: {
pageSize: 25,
pageSizeOptions: [10, 25, 50],
},
density: 'comfortable',
dataDepth: 'standard',
personalization: {
enabled: true,
persist: {
viewMode: true,
density: true,
dataDepth: true,
pageSize: true,
},
},
},
filters: [
{ key: 'status', label: 'Status', field: 'status', type: 'enum' },
{
key: 'amount',
label: 'Amount',
field: 'amount',
type: 'currency',
valueMode: 'range',
},
{
key: 'renewalDate',
label: 'Renewal',
field: 'renewalDate',
type: 'date',
valueMode: 'range',
},
],
},
});The live version of this pattern is available in the canonical Data Grid Showcase
3. Render on the frontend
Use the runtime renderer in your React or React Native app:
'use client';
import { DataViewRenderer } from '@lssm-tech/lib.design-system';
import { TransactionHistory } from '@/lib/specs/billing/transaction-history.data-view';
import { useQuery } from '@tanstack/react-query';
export function TransactionsPage() {
const { data, isLoading } = useQuery({
queryKey: ['transactions'],
queryFn: () => fetch('/api/ops/billing.listTransactions').then(r => r.json()),
});
return (
<div className="container py-8">
<h1 className="text-3xl font-bold mb-6">Payment History</h1>
<DataViewRenderer
spec={TransactionHistory}
items={data?.items ?? []}
loading={isLoading}
defaultViewMode="table"
defaultDensity="comfortable"
defaultDataDepth="standard"
onFilterChange={(filters) => {
// refetch with new filters
}}
/>
</div>
);
}4. Add personalization
When the app has a user profile or behavior insights, resolve DataView preferences before rendering. The renderer receives plain props; personalization stays in the app/runtime boundary.
'use client';
import { DataViewRenderer } from '@lssm-tech/lib.design-system';
import { resolveDataViewPreferences } from '@lssm-tech/lib.personalization/data-view-preferences';
import { createBehaviorTracker } from '@lssm-tech/lib.personalization';
const tracker = createBehaviorTracker({
store,
context: { tenantId: tenant.id, userId: user.id },
});
const resolved = resolveDataViewPreferences({
spec: TransactionHistory,
preferences: profile.canonical,
insights,
record: savedTransactionViewPreference,
});
<DataViewRenderer
spec={TransactionHistory}
items={transactions}
defaultViewMode={resolved.viewMode}
defaultDensity={resolved.density}
defaultDataDepth={resolved.dataDepth}
pagination={{ page, pageSize: resolved.pageSize ?? 25, total }}
onViewModeChange={(viewMode) =>
tracker.trackDataViewInteraction({
dataViewKey: TransactionHistory.meta.key,
action: 'view_mode_changed',
viewMode,
})
}
onDataDepthChange={(dataDepth) =>
tracker.trackDataViewInteraction({
dataViewKey: TransactionHistory.meta.key,
action: 'data_depth_changed',
dataDepth,
})
}
/>;Why DataViews?
Same spec renders on web (React) and mobile (React Native)
Filters, sorting, and pagination handled automatically
Column visibility, pinning, resizing, and row expansion stay contract-driven
List, grid, and table modes can share one collection config with toolbar and pagination defaults
Data depth lets summary screens hide detailed fields without forking the spec
Typed format rules for numbers, percent values, currency, dates, times, datetimes, and durations applied consistently
Personalization helpers can seed preferred view mode, density, data depth, and page size from user preferences or behavior insights
Export to CSV/PDF using the same spec
A/B test different layouts without touching the backend
Developer tools
Use the CLI, editors, and helper tooling that make the OSS workflow practical day to day.
Troubleshooting
Resolve the common installation, validation, and runtime mistakes you hit during first adoption.
Why ContractSpec
Keep educational and comparison content reachable without letting it define the primary OSS learning path.