From 37382b7795417dd4dbf86181392f3538e7e8ed15 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sun, 1 Mar 2026 22:42:53 +0100 Subject: [PATCH 01/90] Use prepare for package build lifecycle Replaced prepack with prepare in publishable package manifests so build runs in git-based installs and standard packaging flows. This keeps direct GitHub package installs working while avoiding reliance on prepack-only behavior. --- packages/common/package.json | 2 +- packages/nodejs/package.json | 2 +- packages/react-native/package.json | 2 +- packages/react-web/package.json | 2 +- packages/react/package.json | 2 +- packages/svelte/package.json | 2 +- packages/vue/package.json | 2 +- packages/web/package.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/common/package.json b/packages/common/package.json index 59ad37327..b694d20da 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -56,7 +56,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepack": "npm run build", + "prepare": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { diff --git a/packages/nodejs/package.json b/packages/nodejs/package.json index fc282d2f7..c603107c5 100644 --- a/packages/nodejs/package.json +++ b/packages/nodejs/package.json @@ -25,7 +25,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepack": "npm run build", + "prepare": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { diff --git a/packages/react-native/package.json b/packages/react-native/package.json index c85104004..2919b7eed 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -74,7 +74,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepack": "npm run build", + "prepare": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { diff --git a/packages/react-web/package.json b/packages/react-web/package.json index a3224f642..407f4ccd8 100644 --- a/packages/react-web/package.json +++ b/packages/react-web/package.json @@ -31,7 +31,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepack": "npm run build", + "prepare": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "devDependencies": { diff --git a/packages/react/package.json b/packages/react/package.json index 4171e6209..df3e4e04f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -31,7 +31,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepack": "npm run build", + "prepare": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "devDependencies": { diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 1b1f8fe26..1ce697251 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -29,7 +29,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build && npm run package", - "prepack": "npm run build", + "prepare": "npm run build", "check": "svelte-check --tsconfig ./tsconfig.json", "package": "svelte-package", "prepublishOnly": "npm run package", diff --git a/packages/vue/package.json b/packages/vue/package.json index f131f8e96..03fdc4182 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -31,7 +31,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepack": "npm run build", + "prepare": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "devDependencies": { diff --git a/packages/web/package.json b/packages/web/package.json index e81c4a48d..51f860f72 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -29,7 +29,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepack": "npm run build", + "prepare": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { From c14c29c5588cb3a8685fe4f9e0b698322b93ea69 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sun, 1 Mar 2026 22:43:18 +0100 Subject: [PATCH 02/90] Add monorepo no-emit typecheck graph Added a root TypeScript solution config for package references and a root typecheck script that runs tsc build mode with noEmit. Also updated verify to run typecheck first so type errors in package tests are caught consistently across the monorepo. --- package.json | 3 ++- tsconfig.typecheck.json | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tsconfig.typecheck.json diff --git a/package.json b/package.json index 95eb4d562..47edcb7c1 100755 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build:web": "bun run build:docs && turbo --filter web build", "build:docs": "typedoc && cd apps/web && bun run fix:docs", "build:expo": "turbo --filter @example/react-expo build", + "typecheck": "tsc --build tsconfig.typecheck.json --noEmit", "test": "vitest run", "test:coverage": "vitest run --coverage", "test:watch": "vitest", @@ -20,7 +21,7 @@ "lint": "eslint --cache", "biome": "biome check", "lint-monorepo": "bunx sherif@latest --ignore-dependency react@19.2.0", - "verify": "bun run build && bun run test:coverage && bun run lint && bun run biome && bun run lint-monorepo && bun run build:docs", + "verify": "bun run typecheck && bun run build && bun run test:coverage && bun run lint && bun run biome && bun run lint-monorepo && bun run build:docs", "clean:dry": "git clean -Xnd", "clean": "git clean -Xdf", "format": "prettier --write \"**/*.{ts,tsx,js,mjs,md,mdx}\"", diff --git a/tsconfig.typecheck.json b/tsconfig.typecheck.json new file mode 100644 index 000000000..030f3de75 --- /dev/null +++ b/tsconfig.typecheck.json @@ -0,0 +1,12 @@ +{ + "files": [], + "references": [ + { "path": "./packages/common" }, + { "path": "./packages/nodejs" }, + { "path": "./packages/react" }, + { "path": "./packages/react-native" }, + { "path": "./packages/react-web" }, + { "path": "./packages/vue" }, + { "path": "./packages/web" } + ] +} From ed8b13a0c8ba501c8119a105d7181fa5f424282b Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sun, 1 Mar 2026 22:45:03 +0100 Subject: [PATCH 03/90] Revert "Use prepare for package build lifecycle" This reverts commit 37382b7795417dd4dbf86181392f3538e7e8ed15. --- packages/common/package.json | 2 +- packages/nodejs/package.json | 2 +- packages/react-native/package.json | 2 +- packages/react-web/package.json | 2 +- packages/react/package.json | 2 +- packages/svelte/package.json | 2 +- packages/vue/package.json | 2 +- packages/web/package.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/common/package.json b/packages/common/package.json index b694d20da..59ad37327 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -56,7 +56,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepare": "npm run build", + "prepack": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { diff --git a/packages/nodejs/package.json b/packages/nodejs/package.json index c603107c5..fc282d2f7 100644 --- a/packages/nodejs/package.json +++ b/packages/nodejs/package.json @@ -25,7 +25,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepare": "npm run build", + "prepack": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 2919b7eed..c85104004 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -74,7 +74,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepare": "npm run build", + "prepack": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { diff --git a/packages/react-web/package.json b/packages/react-web/package.json index 407f4ccd8..a3224f642 100644 --- a/packages/react-web/package.json +++ b/packages/react-web/package.json @@ -31,7 +31,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepare": "npm run build", + "prepack": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "devDependencies": { diff --git a/packages/react/package.json b/packages/react/package.json index df3e4e04f..4171e6209 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -31,7 +31,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepare": "npm run build", + "prepack": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "devDependencies": { diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 1ce697251..1b1f8fe26 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -29,7 +29,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build && npm run package", - "prepare": "npm run build", + "prepack": "npm run build", "check": "svelte-check --tsconfig ./tsconfig.json", "package": "svelte-package", "prepublishOnly": "npm run package", diff --git a/packages/vue/package.json b/packages/vue/package.json index 03fdc4182..f131f8e96 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -31,7 +31,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepare": "npm run build", + "prepack": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "devDependencies": { diff --git a/packages/web/package.json b/packages/web/package.json index 51f860f72..e81c4a48d 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -29,7 +29,7 @@ ], "scripts": { "build": "rimraf dist && tsc --build tsconfig.build.json", - "prepare": "npm run build", + "prepack": "npm run build", "format": "prettier --write \"src/*.{ts,tsx,md}\"" }, "dependencies": { From 461979c5b4207c1e1a27ecba80d7629b66bac45d Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Mon, 2 Mar 2026 12:27:06 +0100 Subject: [PATCH 04/90] Fix typecheck script for project references --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 47edcb7c1..b2d5d2ac4 100755 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build:web": "bun run build:docs && turbo --filter web build", "build:docs": "typedoc && cd apps/web && bun run fix:docs", "build:expo": "turbo --filter @example/react-expo build", - "typecheck": "tsc --build tsconfig.typecheck.json --noEmit", + "typecheck": "tsc --build tsconfig.typecheck.json", "test": "vitest run", "test:coverage": "vitest run --coverage", "test:watch": "vitest", From fc537a7b7a9c103f92ca3b295862b71ef63081a9 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Mon, 2 Mar 2026 15:54:04 +0100 Subject: [PATCH 05/90] Rename App to AppContext and update usages --- .../playgrounds/minimal/EvoluMinimalExample.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx b/apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx index d7616175f..ded62b4df 100644 --- a/apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx +++ b/apps/web/src/app/(playgrounds)/playgrounds/minimal/EvoluMinimalExample.tsx @@ -54,8 +54,6 @@ const console = Evolu.createConsole({ formatter: Evolu.createConsoleFormatter()({ timestampFormat: "relative" }), }); -// console.log(Symbol.dispose); - // TODO: createRunWithEvoluDeps() // Create Evolu dependencies for React Web. @@ -87,7 +85,7 @@ const app = run( }), ); -const [App, AppProvider] = createEvoluContext(app); +const [AppContext, AppProvider] = createEvoluContext(app); export const EvoluMinimalExample: FC = () => (
@@ -119,7 +117,8 @@ const parseTodoTitle = (value: string) => const Todos: FC = () => { // useQuery returns live data - component re-renders when data changes. const todos = useQuery(todosQuery); - const { insert } = use(App); + + const { insert } = use(AppContext); const [newTodoTitle, setNewTodoTitle] = useState(""); const addTodo = () => { @@ -172,7 +171,7 @@ const Todos: FC = () => { const TodoItem: FC<{ row: TodosRow; }> = ({ row: { id, title, isCompleted } }) => { - const { update } = use(App); + const { update } = use(AppContext); const handleToggleCompletedClick = () => { update("todo", { @@ -241,7 +240,7 @@ const TodoItem: FC<{ }; const OwnerActions: FC = () => { - const evolu = use(App); + const evolu = use(AppContext); const [showMnemonic, setShowMnemonic] = useState(false); From 637655fb5eca3a02a4eb4c41d0835528917f1119 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Mon, 2 Mar 2026 16:34:47 +0100 Subject: [PATCH 06/90] Add build:docs entry to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f3c3e1584..dbcb6b03a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ bun install Build scripts - `bun run build` - Build packages (required once after clone/pull for IDE types) +- `bun run build:docs` - Build doc (required once after clone/pull) - `bun run build:web` - Build docs and web Web build notes From 9469559145fcf30235c5fd5d6da397db43b8b753 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Mon, 2 Mar 2026 16:36:36 +0100 Subject: [PATCH 07/90] Document Kysely relation helpers in Schema --- packages/common/src/local-first/Schema.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/common/src/local-first/Schema.ts b/packages/common/src/local-first/Schema.ts index ca3582294..1202e31ff 100644 --- a/packages/common/src/local-first/Schema.ts +++ b/packages/common/src/local-first/Schema.ts @@ -34,6 +34,7 @@ import type { AppOwner } from "./Owner.js"; import { OwnerId } from "./Owner.js"; import type { Query, Row } from "./Query.js"; import { serializeQuery } from "./Query.js"; +import type { jsonArrayFrom, jsonObjectFrom } from "./Kysely.js"; import type { CrdtMessage, DbChange } from "./Storage.js"; import { TimestampBytes } from "./Timestamp.js"; @@ -392,6 +393,11 @@ export const evoluSchemaToSqliteSchema = ( /** * Creates a query builder from a {@link EvoluSchema}. * + * Supports Kysely relation-style query composition (nested objects/arrays via + * JSON subqueries), such as {@link jsonObjectFrom} and {@link jsonArrayFrom} from + * the + * {@link https://kysely.dev/docs/recipes/relations | Kysely relations recipe}. + * * ### Example * * ```ts From 00fb70a08debb08d41a7d8b99091f459cf7e6528 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Mon, 2 Mar 2026 16:37:51 +0100 Subject: [PATCH 08/90] Restore full Evolu playground example --- .../playgrounds/full/EvoluFullExample.tsx | 1866 +++++++++-------- .../(playgrounds)/playgrounds/full/page.tsx | 3 + 2 files changed, 942 insertions(+), 927 deletions(-) diff --git a/apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx b/apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx index 60ea65b6a..568953516 100644 --- a/apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx +++ b/apps/web/src/app/(playgrounds)/playgrounds/full/EvoluFullExample.tsx @@ -1,929 +1,941 @@ "use client"; -import type { FC } from "react"; - -export const EvoluFullExample: FC = () =>
TODO
; - -// import { -// booleanToSqliteBoolean, -// createEvolu, -// createFormatTypeError, -// createObjectURL, -// createQueryBuilder, -// FiniteNumber, -// id, -// idToIdBytes, -// json, -// kysely, -// maxLength, -// Mnemonic, -// NonEmptyString, -// NonEmptyTrimmedString100, -// nullOr, -// object, -// Name, -// SqliteBoolean, -// sqliteFalse, -// sqliteTrue, -// timestampBytesToTimestamp, -// type MaxLengthError, -// type MinLengthError, -// } from "@evolu/common"; -// import { timestampToDateIso } from "@evolu/common/local-first"; -// import { -// createUseEvolu, -// EvoluProvider, -// useQueries, -// useQuery, -// } from "@evolu/react"; -// import { createEvoluDeps } from "@evolu/react-web"; -// import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; -// import { -// IconChecklist, -// IconEdit, -// IconHistory, -// IconRestore, -// IconTrash, -// } from "@tabler/icons-react"; -// import clsx from "clsx"; -// import { -// startTransition, -// Suspense, -// use, -// useState, -// type FC, -// type KeyboardEvent, -// } from "react"; - -// // TODO: Epochs and sharing. - -// const ProjectId = id("Project"); -// type ProjectId = typeof ProjectId.Type; - -// const TodoId = id("Todo"); -// type TodoId = typeof TodoId.Type; - -// // A custom branded Type. -// const NonEmptyString50 = maxLength(50)(NonEmptyString); -// // string & Brand<"MinLength1"> & Brand<"MaxLength50"> -// type NonEmptyString50 = typeof NonEmptyString50.Type; - -// // SQLite supports JSON values. -// // Use JSON for semi-structured data like API responses, external integrations, -// // or when the schema varies by use case. -// // Let's create an object to demonstrate it. -// const Foo = object({ -// foo: NonEmptyString50, -// // Did you know that JSON.stringify converts NaN (a number) into null? -// // To prevent this, use FiniteNumber. -// bar: FiniteNumber, -// }); -// type Foo = typeof Foo.Type; - -// // SQLite stores JSON values as strings. Evolu provides a convenient `json` -// // Type Factory for type-safe JSON serialization and parsing. -// const [FooJson, fooToFooJson, fooJsonToFoo] = json(Foo, "FooJson"); -// // string & Brand<"FooJson"> -// type FooJson = typeof FooJson.Type; - -// const Schema = { -// project: { -// id: ProjectId, -// name: NonEmptyTrimmedString100, -// fooJson: FooJson, -// }, -// todo: { -// id: TodoId, -// title: NonEmptyTrimmedString100, -// isCompleted: nullOr(SqliteBoolean), -// projectId: nullOr(ProjectId), -// }, -// }; - -// const createQuery = createQueryBuilder(Schema); - -// const deps = createEvoluDeps(); - -// const evolu = createEvolu(deps)(Schema, { -// name: Name.orThrow("full-example"), - -// // reloadUrl: "/playgrounds/full", - -// ...(process.env.NODE_ENV === "development" && { -// transports: [{ type: "WebSocket", url: "ws://localhost:4000" }], - -// // Empty transports for local-only instance. -// // transports: [], -// }), - -// // https://www.evolu.dev/docs/indexes -// indexes: (create) => [ -// create("todoCreatedAt").on("todo").column("createdAt"), -// create("projectCreatedAt").on("project").column("createdAt"), -// create("todoProjectId").on("todo").column("projectId"), -// ], - -// // enableLogging: false, -// }); - -// const useEvolu = createUseEvolu(evolu); - -// evolu.subscribeError(() => { -// const error = evolu.getError(); -// if (!error) return; - -// alert("🚨 Evolu error occurred! Check the console."); -// // eslint-disable-next-line no-console -// console.error(error); -// }); - -// export const EvoluFullExample: FC = () => ( -//
-//
-// -// -// -// -// -//
-//
-// ); - -// const App: FC = () => { -// const [activeTab, setActiveTab] = useState< -// "home" | "projects" | "account" | "trash" -// >("home"); - -// const createHandleTabClick = (tab: typeof activeTab) => () => { -// // startTransition prevents UI flickers when switching tabs by keeping -// // the current view visible while Suspense prepares the next one -// // Test: Remove startTransition, add a todo, delete it, click to Trash. -// // You will see a visible blink without startTransition. -// startTransition(() => { -// setActiveTab(tab); -// }); -// }; - -// return ( -//
-//
-//
-// -// -// -// -//
-//
- -// {activeTab === "home" && } -// {activeTab === "projects" && } -// {activeTab === "account" && } -// {activeTab === "trash" && } -//
-// ); -// }; - -// const projectsWithTodosQuery = createQuery( -// (db) => -// db -// .selectFrom("project") -// .select(["id", "name"]) -// // https://kysely.dev/docs/recipes/relations -// .select((eb) => [ -// kysely -// .jsonArrayFrom( -// eb -// .selectFrom("todo") -// .select([ -// "todo.id", -// "todo.title", -// "todo.isCompleted", -// "todo.projectId", -// ]) -// .whereRef("todo.projectId", "=", "project.id") -// .where("todo.isDeleted", "is not", sqliteTrue) -// .where("todo.title", "is not", null) -// .$narrowType<{ title: kysely.NotNull }>() -// .orderBy("createdAt"), -// ) -// .as("todos"), -// ]) -// .where("project.isDeleted", "is not", sqliteTrue) -// .where("name", "is not", null) -// .$narrowType<{ name: kysely.NotNull }>() -// .orderBy("createdAt"), -// { -// // Log how long each query execution takes -// logQueryExecutionTime: false, - -// // Log the SQLite query execution plan for optimization analysis -// logExplainQueryPlan: false, -// }, -// ); - -// type ProjectsWithTodosRow = typeof projectsWithTodosQuery.Row; - -// const HomeTab: FC = () => { -// const [projectsWithTodos, projects] = useQueries([ -// projectsWithTodosQuery, -// /** -// * Load projects separately for better cache efficiency. Projects change -// * less frequently than todos, preventing unnecessary re-renders. Multiple -// * queries are fine in local-first - no network overhead. -// */ -// projectsQuery, -// ]); - -// const handleAddProjectClick = useAddProject(); - -// if (projectsWithTodos.length === 0) { -// return ( -//
-//
-// -//
-//

-// No projects yet -//

-//

-// Create your first project to get started -//

-//
-// ); -// } - -// return ( -//
-//
-// {projectsWithTodos.map((project) => ( -// -// ))} -//
-//
-// ); -// }; - -// const HomeTabProject: FC<{ -// project: ProjectsWithTodosRow; -// todos: ProjectsWithTodosRow["todos"]; -// projects: ReadonlyArray; -// }> = ({ project, todos, projects }) => { -// const { insert } = useEvolu(); -// const [newTodoTitle, setNewTodoTitle] = useState(""); - -// const addTodo = () => { -// const result = insert( -// "todo", -// { -// title: newTodoTitle.trim(), -// projectId: project.id, -// }, -// { -// onComplete: () => { -// setNewTodoTitle(""); -// }, -// }, -// ); - -// if (!result.ok) { -// alert(formatTypeError(result.error)); -// } -// }; - -// const handleKeyPress = (e: KeyboardEvent) => { -// if (e.key === "Enter") { -// addTodo(); -// } -// }; - -// return ( -//
-//
-//

-// -// {project.name} -//

-//
- -// {todos.length > 0 && ( -//
    -// {todos.map((todo) => ( -// -// ))} -//
-// )} - -//
-// { -// setNewTodoTitle(e.target.value); -// }} -// data-1p-ignore // ignore this input from 1password, ugly hack but works -// onKeyDown={handleKeyPress} -// placeholder="Add a new todo..." -// className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6" -// /> -//
-//
-// ); -// }; - -// const HomeTabProjectSectionTodoItem: FC<{ -// // [number] extracts the element type from the todos array -// row: ProjectsWithTodosRow["todos"][number]; -// projects: ReadonlyArray; -// }> = ({ row: { id, title, isCompleted, projectId }, projects }) => { -// const { update } = useEvolu(); - -// const handleToggleCompletedClick = () => { -// // No need to check result if a mutation can't fail. -// update("todo", { -// id, -// isCompleted: booleanToSqliteBoolean(!isCompleted), -// }); -// }; - -// const handleProjectChange = (newProjectId: ProjectId) => { -// update("todo", { id, projectId: newProjectId }); -// }; - -// const handleRenameClick = () => { -// const newTitle = window.prompt("Edit todo", title); -// if (newTitle == null) return; - -// const result = update("todo", { id, title: newTitle.trim() }); -// if (!result.ok) { -// alert(formatTypeError(result.error)); -// } -// }; - -// const handleDeleteClick = () => { -// update("todo", { id, isDeleted: sqliteTrue }); -// }; - -// // Demonstrate history tracking. Evolu automatically tracks all changes -// // in the evolu_history table, making it easy to build audit logs or undo features. -// const titleHistoryQuery = createQuery((db) => -// db -// .selectFrom("evolu_history") -// .select(["value", "timestamp"]) -// .where("table", "==", "todo") -// .where("id", "==", idToIdBytes(id)) -// .where("column", "==", "title") -// // The value isn't typed; this is how we can cast it. -// .$narrowType<{ value: (typeof Schema)["todo"]["title"]["Type"] }>() -// .orderBy("timestamp", "desc"), -// ); - -// const handleHistoryClick = () => { -// void evolu.loadQuery(titleHistoryQuery).then((rows) => { -// const rowsWithTimestamp = rows.map((row) => ({ -// value: row.value, -// timestamp: timestampToDateIso(timestampBytesToTimestamp(row.timestamp)), -// })); -// alert(JSON.stringify(rowsWithTimestamp, null, 2)); -// }); -// }; - -// return ( -//
  • -// -//
    -//
    -// -// -// -// -// -//
    -// {projects.map((project) => ( -// -// -// -// ))} -//
    -//
    -//
    -// -// -// -//
    -//
    -//
  • -// ); -// }; - -// const projectsQuery = createQuery((db) => -// db -// .selectFrom("project") -// .select(["id", "name", "fooJson"]) -// .where("isDeleted", "is not", sqliteTrue) -// .where("name", "is not", null) -// .$narrowType<{ name: kysely.NotNull }>() -// .where("fooJson", "is not", null) -// .$narrowType<{ fooJson: kysely.NotNull }>() -// .orderBy("createdAt"), -// ); - -// type ProjectsRow = typeof projectsQuery.Row; - -// const useAddProject = () => { -// const { insert } = useEvolu(); - -// return () => { -// const name = window.prompt("What's the project name?"); -// if (name == null) return; - -// // Demonstrate JSON usage. -// const foo = Foo.from({ foo: "baz", bar: 42 }); -// if (!foo.ok) return; - -// const result = insert("project", { -// name: name.trim(), -// fooJson: fooToFooJson(foo.value), -// }); -// if (!result.ok) { -// alert(formatTypeError(result.error)); -// } -// }; -// }; - -// const ProjectsTab: FC = () => { -// const projects = useQuery(projectsQuery); -// const handleAddProjectClick = useAddProject(); - -// return ( -//
    -//
    -// {projects.map((project) => ( -// -// ))} -//
    -//
    -//
    -//
    -// ); -// }; - -// const ProjectsTabProjectItem: FC<{ -// project: ProjectsRow; -// }> = ({ project }) => { -// const { update } = useEvolu(); - -// const handleRenameClick = () => { -// const newName = window.prompt("Edit project name", project.name); -// if (newName == null) return; - -// const result = update("project", { id: project.id, name: newName.trim() }); -// if (!result.ok) { -// alert(formatTypeError(result.error)); -// } -// }; - -// const handleDeleteClick = () => { -// if (confirm(`Are you sure you want to delete project "${project.name}"?`)) { -// /** -// * In a classic centralized client-server app, we would fetch all todos -// * for this project and delete them too. But that approach is wrong for -// * distributed eventually consistent systems for two reasons: -// * -// * 1. Sync overhead scales with todo count (a project with 10k todos would -// * generate 10k sync messages instead of just 1 for the project) -// * 2. It wouldn't delete todos from other devices before they sync -// * -// * The correct approach for local-first systems: handle cascading logic in -// * the UI layer. Queries filter out deleted projects, so their todos -// * naturally become hidden. If a todo detail view is needed, it should -// * check whether its parent project was deleted. -// */ -// update("project", { -// id: project.id, -// isDeleted: sqliteTrue, -// }); -// } -// }; - -// // Demonstrate JSON deserialization. Because FooJson is a branded type, -// // we can safely deserialize without validation - TypeScript guarantees -// // only validated JSON strings can have the FooJson brand. -// const _foo = fooJsonToFoo(project.fooJson); - -// return ( -//
    -//
    -// -//
    -//

    {project.name}

    -//
    -//
    -//
    -// -// -//
    -//
    -// ); -// }; - -// const AccountTab: FC = () => { -// const evolu = useEvolu(); -// const appOwner = use(evolu.appOwner); - -// const [showMnemonic, setShowMnemonic] = useState(false); - -// const handleRestoreAppOwnerClick = () => { -// const mnemonic = window.prompt("Enter your mnemonic to restore your data:"); -// if (mnemonic == null) return; - -// const result = Mnemonic.from(mnemonic.trim()); -// if (!result.ok) { -// alert(formatTypeError(result.error)); -// return; -// } - -// // void evolu.restoreAppOwner(result.value); -// }; - -// const handleResetAppOwnerClick = () => { -// if (confirm("Are you sure? This will delete all your local data.")) { -// // void evolu.resetAppOwner(); -// } -// }; - -// const handleDownloadDatabaseClick = () => { -// void evolu.exportDatabase().then((data) => { -// using objectUrl = createObjectURL( -// new Blob([data], { type: "application/x-sqlite3" }), -// ); - -// const link = document.createElement("a"); -// link.href = objectUrl.url; -// link.download = `${evolu.name}.sqlite3`; -// link.click(); -// }); -// }; - -// return ( -//
    -//

    -// Todos are stored in local SQLite. When you sync across devices, your -// data is end-to-end encrypted using your mnemonic. -//

    - -//
    -//