Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
"dependencies": {
"@base-ui/react": "^1.6.0",
"@pascal-app/lingo": "workspace:*",
"@tanstack/react-table": "^8.21.3",
"@vercel/analytics": "^2.0.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"geist": "^1.7.2",
"katex": "^0.18.1",
"lucide-react": "^1.23.0",
"marked": "^18.0.7",
"motion": "^12.42.2",
Expand All @@ -33,6 +35,7 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/katex": "^0.16.8",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
Expand Down
55 changes: 55 additions & 0 deletions apps/site/src/app/docs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { IntegrationsTabs } from '@/app/integrations/integrations-tabs'
import { AiCanonicalizerDemo } from '@/components/site/ai-canonicalizer-demo'
import { AiEvalReadout } from '@/components/site/ai-eval-readout'
import { SectionHeading, SubHeading } from '@/components/site/anchor-heading'
import { CalendarFieldDemo } from '@/components/site/calendar-field-demo'
import { CodeBlock } from '@/components/site/code-block'
import { CodeTabs } from '@/components/site/code-tabs'
import { CommandBlock } from '@/components/site/command-block'
import { CompletionsDemo } from '@/components/site/completions-demo'
import { CoverageExplorer } from '@/components/site/coverage-explorer'
import { DataGridDemo } from '@/components/site/data-grid-demo'
import { DocsNav } from '@/components/site/docs-nav'
import { DocsPageActions } from '@/components/site/docs-page-actions'
import { FormUxGallery } from '@/components/site/form-ux-gallery'
import { LatexUnitsDemo } from '@/components/site/latex-units-demo'
import { ParsePlayground } from '@/components/site/parse-playground'
import { PerformanceSection } from '@/components/site/performance-section'
import {
Expand Down Expand Up @@ -189,6 +192,23 @@ const formSchemaSnippet = `import { standardSchemaResolver } from '@hookform/res
// user types "5 kg" / picks a date; canonical on submit
useForm({ resolver: standardSchemaResolver(shipment) })`

const gridColumnSnippet = `// The column owns the unit; the cell owns nothing but text.
const columns = {
mass: quantityField({ kind: 'mass', unit: 'kg' }),
temp: quantityField({ kind: 'temperature', unit: 'C' }),
price: quantityField({ kind: 'currency', unit: 'USD' }),
shipBy: dateField({ now }),
}

function cell(column: keyof typeof columns, text: string) {
const { value, warnings, issues } = columns[column].safeParse(text)
if (issues) {
return { state: 'refused', note: issues[0].message }
}
// "$12.50" resolved to USD, "half a ton" to a short ton — say so.
return { state: warnings ? 'assumed' : 'ok', value, warnings }
}`

// Sourced from the package so the badge wall can't drift from IssueCode.
const issueCodes = Object.keys(ISSUE_CODES)

Expand Down Expand Up @@ -832,6 +852,18 @@ export default async function Home() {
</code>{' '}
for fields lingo doesn&apos;t own.)
</p>
<div className="flex min-w-0 flex-col gap-3">
<SubHeading id="one-schema-grid">A column is a schema</SubHeading>
<p className="text-muted-foreground text-sm">
The same idea scales past a single field. Give a table column a{' '}
<Code>quantityField</Code> and a cell can take any notation that column can resolve
— pounds and ounces, a comma decimal, Fahrenheit — normalizing into one canonical
unit, so the totals row can just add numbers. What it cannot resolve stays an issue
on the cell that caused it.
</p>
</div>
<CodeBlock code={gridColumnSnippet} filename="columns.ts" lang="ts" />
<DataGridDemo />
</Section>

<Section
Expand All @@ -847,6 +879,17 @@ export default async function Home() {
rise is a 9 °F rise, not 41. Everything <Code>format()</Code> emits re-parses to the
same value.
</p>
<div className="flex min-w-0 flex-col gap-3">
<SubHeading id="convert-notation">Typeset the reading</SubHeading>
<p className="text-muted-foreground text-sm">
A canonical reading is structured enough to render as notation, not just text. The
unit id and the numeric value are separate fields, so <Code>m/s2</Code> becomes a
real fraction with a superscript and <Code>±</Code> tolerance becomes a proper
interval. This demo maps results to LaTeX in ~90 lines; lingo itself ships no
renderer.
</p>
</div>
<LatexUnitsDemo />
</Section>

<Section
Expand Down Expand Up @@ -888,6 +931,18 @@ export default async function Home() {
</a>
.
</p>
<div className="flex min-w-0 flex-col gap-3">
<SubHeading id="dates-calendar">One field, three readings</SubHeading>
<p className="text-muted-foreground text-sm">
<Code>parseDateRange</Code> also reads date-to-date spans (
<Code>Aug 3 - Aug 9</Code>) and whole calendar periods (<Code>next week</Code>,{' '}
<Code>this weekend</Code>, <Code>next month</Code>), each expanded to its real first
and last day. Because the reading says which shape it found, one input can decide
between a day picker, a two-month range picker, and a time slot — no mode toggle for
the person typing.
</p>
</div>
<CalendarFieldDemo />
</Section>

<Section
Expand Down
Loading
Loading