diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index 490475c..ee78ffb 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -161,66 +161,101 @@ body { margin-left: 0.375em; } -.triplex-home__code { - margin-top: 28px; +.triplex-home__section { + margin-top: 64px; + padding-top: 32px; + border-top: 1px solid var(--vp-c-divider); +} + +.triplex-home__section-intro { + max-width: 760px; + margin-bottom: 28px; +} + +.triplex-home .triplex-home__eyebrow { + margin: 0 0 10px; + color: var(--vp-c-brand-1); + font-family: var(--vp-font-family-mono); + font-size: 0.72rem; + letter-spacing: 0.06em; + text-transform: uppercase; } -.triplex-home__flow { +.triplex-home__section h2 { + margin: 0 0 16px; + padding: 0; + border: 0; + font-size: clamp(1.5rem, 3vw, 1.9rem); + line-height: 1.25; +} + +.triplex-home__section h3 { + margin: 0 0 10px; + font-size: 1.1rem; + line-height: 1.4; +} + +.triplex-home__section p { + color: var(--vp-c-text-2); + line-height: 1.7; +} + +.triplex-home__principles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 24px; + margin-top: 28px; +} + +.triplex-home__principles > div { + padding: 20px; border: 1px solid var(--vp-c-divider); - border-bottom: 0; - border-radius: 7px 7px 0 0; - background: color-mix(in srgb, var(--vp-c-bg-elv) 72%, var(--vp-c-bg)); + border-radius: 7px; + background: var(--vp-c-bg-elv); } -.triplex-home__flow > div { - min-width: 0; - padding: 13px 16px 14px; +.triplex-home__principles p { + margin: 0; + font-size: 0.95rem; } -.triplex-home__flow > div + div { - border-left: 1px solid var(--vp-c-divider); +.triplex-home .triplex-home__fit { + margin: 24px 0 0; + font-size: 0.95rem; } -.triplex-home__flow span, -.triplex-home__flow strong { - display: block; +.triplex-home__example { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 2.2fr); + align-items: start; + gap: 28px; + margin-top: 32px; } -.triplex-home__flow span { - color: var(--vp-c-brand-1); - font-family: var(--vp-font-family-mono); - font-size: 0.66rem; - letter-spacing: 0.075em; - text-transform: uppercase; +.triplex-home__example + .triplex-home__example { + padding-top: 32px; + border-top: 1px solid var(--vp-c-divider); } -.triplex-home__flow strong { - margin-top: 5px; - overflow: hidden; - color: var(--vp-c-text-1); - font-family: var(--vp-font-family-mono); - font-size: 0.77rem; - font-weight: 400; - text-overflow: ellipsis; - white-space: nowrap; +.triplex-home__example-description p { + margin: 0 0 16px; + font-size: 0.95rem; } -.triplex-home__code .vp-code-group { - margin: 0; - border: 1px solid var(--vp-c-divider); - border-radius: 0 0 7px 7px; - overflow: hidden; +.triplex-home__example-description a { + display: inline-flex; + align-items: center; + font-size: 0.95rem; } -.triplex-home__code .tabs { - margin: 0; - border-radius: 0; +.triplex-home__snippet { + min-width: 0; } -.triplex-home__code div[class*="language-"] { +.triplex-home__snippet div[class*="language-"] { margin: 0; + border: 1px solid var(--vp-c-divider); + border-radius: 7px; box-shadow: none; } @@ -264,12 +299,16 @@ body { padding: 36px 0 44px; } - .triplex-home__flow { - grid-template-columns: 1fr; + .triplex-home__section { + margin-top: 40px; + padding-top: 28px; } +} - .triplex-home__flow > div + div { - border-top: 1px solid var(--vp-c-divider); - border-left: 0; +@media (max-width: 800px) { + .triplex-home__example, + .triplex-home__principles { + grid-template-columns: 1fr; + gap: 20px; } } diff --git a/docs/index.md b/docs/index.md index cdba6cc..0d32df6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,54 +16,126 @@ PostgreSQL is a production candidate, while Cloudflare and FoundationDB are expe :::
-
-

Triplex

-

A temporal fact database for TypeScript.

-

+

+

Triplex

+

A temporal fact database for TypeScript.

+

Store facts. Query relationships. Derive state. Keep the complete history.
Bitemporal facts, Datalog, and typed, content-addressed configuration—built on Effect. -

-
- -
- -
-
- 01 · Facts - student → submitted → quiz -
-
- 02 · Datalog - submission without a grade -
-
- 03 · Current state - grading task · open -
-
- -::: code-group + +GitHub +
+ -<<< @/snippets/home/query.ts{ts twoslash} +
+
+

Why Triplex?

+

Your application should be able to explain itself.

+

Current state tells you what is true now. Explaining why often means piecing together database rows, audit logs, policy versions, and background jobs. Those systems rarely share the same view of time.

+

Triplex connects facts, history, configuration, and derived state through a shared identity model and provenance chain. You can trace an answer back to the data and the versioned rules behind it.

+
+
+
+

What did we know?

+

Read facts as they were recorded at a point in time, or as they were valid in the world. Corrections preserve the assertion history.

+
+
+

Which rules applied?

+

Pin a write to an immutable configuration release. Keep the exact schema and policy version available after the live configuration changes.

+
+
+

What needs to happen?

+

Query relationships and missing evidence to derive work. Your application decides how to turn those results into tasks and workflows.

+
+
+

Built for domains where history and rules matter: compliance, onboarding, eligibility, entitlements, and agent-driven workflows.

+
+ +
+
+

Facts → queries → work

+

From a submission to an open grading task.

+

A student submits a quiz. Until a grade exists, that submission implies work. Here is how the facts and query fit together.

+
+ +
+
+

01 · Facts

+

Record what happened.

+

Assert the submission’s relationships in one atomic transaction, with the actor, command, and governing configuration recorded alongside it.

+Explore transactions +
+
<<< @/snippets/home/facts.ts{ts twoslash} +
+
+ +
+
+

02 · Datalog

+

Ask what still needs doing.

+

Join facts by shared variables and find submissions without a grade. Once a grade is present, the submission no longer matches.

+

A derivation can track changes to these results and retain their source provenance, so your application can reconcile its grading tasks.

+Explore derived facts +
+
+ +<<< @/snippets/home/query.ts{ts twoslash} + +
+
+ +
+ +
+
+

Configuration & versioning

+

Version the rules alongside the facts.

+

Schemas, forms, policies, and routines change. Triplex stores their typed configuration graph as immutable, content-addressed releases, so a new deployment does not erase the rules that governed an earlier decision.

+
+ +
+
+

03 · Typed configuration

+

Define once. Publish a release.

+

Attributes define identity and value type. Entity types define how those attributes are used, including requiredness and uniqueness.

+

Commit those definitions with your application’s other configuration into one release, and point live at its snapshot.

+Explore configuration +
+
+ <<< @/snippets/home/ontology.ts{ts twoslash} -::: +
+
+ +
+
+

Immutable releases

+

Content determines identity. Equal nodes deduplicate, and unchanged revisions are shared between releases. Each snapshot preserves an exact configuration graph.

+
+
+

Movable refs

+

Names such as test and live point to snapshots. Promote a release or point back to an earlier one without copying configuration. Moving a ref does not undo operational writes.

+
+
+

Pinned provenance

+

Attach the release’s snapshot ID to a transaction with configSnapshot. Later, inspect the exact version that governed the write, even after live moves.

+
+
-
+ - + diff --git a/e2e/docs-home.spec.ts b/e2e/docs-home.spec.ts index 12b8275..f220d23 100644 --- a/e2e/docs-home.spec.ts +++ b/e2e/docs-home.spec.ts @@ -13,7 +13,18 @@ for (const colorScheme of ["light", "dark"] as const) { await expect(root).not.toHaveClass(/dark/); } await expect(page.getByRole("heading", { level: 1, name: "Triplex" })).toBeVisible(); - await expect(page.locator(".triplex-home__code")).toBeVisible(); + const examples = page.locator('.triplex-home__snippet div[class*="language-"]'); + await expect(examples).toHaveCount(3); + for (const example of await examples.all()) { + await expect(example).toBeVisible(); + } + await expect(page.getByRole("tab")).toHaveCount(0); + await expect( + page.getByRole("heading", { name: "Your application should be able to explain itself." }), + ).toBeVisible(); + await expect( + page.getByRole("heading", { name: "Version the rules alongside the facts." }), + ).toBeVisible(); await expect(page).toHaveScreenshot(`home-${colorScheme}.png`, { animations: "disabled", @@ -21,3 +32,14 @@ for (const colorScheme of ["light", "dark"] as const) { }); }); } + +test("home examples remain visible on mobile without page overflow", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/"); + const examples = page.locator('.triplex-home__snippet div[class*="language-"]'); + await expect(examples).toHaveCount(3); + for (const example of await examples.all()) { + await expect(example).toBeVisible(); + } + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); +}); diff --git a/e2e/docs-home.spec.ts-snapshots/home-dark.png b/e2e/docs-home.spec.ts-snapshots/home-dark.png index 1cfbb0f..b7c9ecb 100644 Binary files a/e2e/docs-home.spec.ts-snapshots/home-dark.png and b/e2e/docs-home.spec.ts-snapshots/home-dark.png differ diff --git a/e2e/docs-home.spec.ts-snapshots/home-light.png b/e2e/docs-home.spec.ts-snapshots/home-light.png index e1de514..1bc39d5 100644 Binary files a/e2e/docs-home.spec.ts-snapshots/home-light.png and b/e2e/docs-home.spec.ts-snapshots/home-light.png differ