From b831a98bb9c750848fb455a20be505e7d5fafdfd Mon Sep 17 00:00:00 2001 From: Heliton Nordt <1625399+hnordt@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:17:12 -0300 Subject: [PATCH 01/20] chore: establish local package workspaces --- AGENTS.md | 1 + .../adr/0001-path-and-pathname-terminology.md | 90 +++++++++++++++++++ docs/adr/README.md | 49 ++++++++++ docs/design/0005-local-package-workspaces.md | 86 ++++++++++++++++++ docs/design/0006-sqlite-connection-package.md | 84 +++++++++++++++++ docs/design/README.md | 54 ++++++----- package-lock.json | 29 +++++- package.json | 6 +- packages/kernel/package.json | 11 +++ packages/kernel/src/index.ts | 7 ++ packages/sqlite/package.json | 14 +++ packages/sqlite/src/index.ts | 28 ++++++ src/lib/kernel-package.test.ts | 6 ++ src/lib/server/db/index.ts | 18 +--- src/lib/server/db/open-database.test.ts | 21 +++++ 15 files changed, 461 insertions(+), 43 deletions(-) create mode 100644 docs/adr/0001-path-and-pathname-terminology.md create mode 100644 docs/adr/README.md create mode 100644 docs/design/0005-local-package-workspaces.md create mode 100644 docs/design/0006-sqlite-connection-package.md create mode 100644 packages/kernel/package.json create mode 100644 packages/kernel/src/index.ts create mode 100644 packages/sqlite/package.json create mode 100644 packages/sqlite/src/index.ts create mode 100644 src/lib/kernel-package.test.ts create mode 100644 src/lib/server/db/open-database.test.ts diff --git a/AGENTS.md b/AGENTS.md index 7c6f7e2..187201c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -197,6 +197,7 @@ Infer TypeScript types from Zod schemas instead of duplicating manual interfaces - Prefer domain names over generic names such as `data`, `item`, `handler`, `manager`, `helper`, or `utils`. - Prefer standard ECMAScript, Web Platform, Svelte, SvelteKit, and CSS APIs over project-specific wrappers. - Do not extract a one-use helper unless it creates a real boundary, names a domain invariant, or materially reduces complexity. +- Do not introduce a one-use local merely to restate a short, readable expression. Keep a local when it names a domain concept or boundary, avoids repeated or side-effectful evaluation, enables type narrowing or cleanup, or materially improves readability. - Avoid unrelated renaming, formatting, file movement, or refactoring. - Preserve public contracts unless the task explicitly changes them. - Separate verified implementation from target architecture in code comments and documentation. diff --git a/docs/adr/0001-path-and-pathname-terminology.md b/docs/adr/0001-path-and-pathname-terminology.md new file mode 100644 index 0000000..209540f --- /dev/null +++ b/docs/adr/0001-path-and-pathname-terminology.md @@ -0,0 +1,90 @@ +# Path and pathname terminology + +| Field | Value | +| ------------- | ---------------------------------------------------- | +| Status | Accepted | +| Scope | Public APIs, configuration, and implementation names | +| Decision date | 2026-07-27 | + +## Summary + +Hyperkernel uses `path` for a general location accepted by an API, and +`pathname` only for the path component of a URL. The two names are not +interchangeable. + +## Problem + +Location-like inputs can be filesystem paths, opaque identifiers accepted by an +API, complete URLs, or one component of a URL. Calling each of them `path` or +`pathname` without a rule makes public APIs harder to understand and obscures +what inputs they accept. + +## Decision + +Use `path` when an API accepts a general location. It may be a filesystem path, +an opaque location syntax documented by that API, or a complete `URL` when the +underlying API accepts one. + +```ts +const configurationPath = "./config/hyperkernel.json"; +const memoryPath = ":memory:"; +declare const resourcePath: URL; +``` + +Use `pathname` only when referring to the pathname component of a URL. It is a +string such as `/workspaces/acme`, not the complete URL. + +```ts +const url = new URL("https://example.test/workspaces/acme?tab=events"); + +url.pathname; // "/workspaces/acme" +url.href; // "https://example.test/workspaces/acme?tab=events" +``` + +Use `url` when an API specifically requires a complete URL and does not accept +another kind of location. + +```ts +function redirect(url: URL): Response { + return Response.redirect(url); +} +``` + +This ADR governs names in programmatic APIs, configuration fields, types, and +developer documentation. It does not prohibit ordinary prose such as "command +execution path" where no location value is being named. + +## Examples + +| Value or concept | Name | Reason | +| ---------------------------------------------------------------------- | ---------- | -------------------------------------------------------------- | +| `./config/hyperkernel.json` supplied to a configuration reader | `path` | It is a filesystem location. | +| `:memory:` supplied to a storage API | `path` | It is an API-specific location, not a URL pathname. | +| `new URL("file:///var/lib/hyperkernel.db")` supplied to a resource API | `path` | The receiving API accepts a general location, including a URL. | +| `/projects/hyperkernel` from `new URL(...).pathname` | `pathname` | It is precisely the URL pathname component. | +| `https://example.test/projects/hyperkernel?view=events` | `url` | The complete URL includes origin and query string. | +| A route matcher input derived from `request.url` | `pathname` | Routing commonly operates on only the URL pathname. | + +## Alternatives considered + +### Use `path` for every location-like value + +Rejected. It would obscure whether a value is a complete URL or only its path +component. + +### Use `pathname` for all path values + +Rejected. `pathname` has a precise URL-specific meaning and does not describe +filesystem paths or API-specific location syntax. + +## Consequences + +API names communicate valid input shapes without relying on implementation +knowledge. A `pathname` must never be documented as a complete URL, and a +`path` must not be assumed to be a URL pathname. + +## Status history + +| Date | Status | Note | +| ---------- | -------- | ------------------------------------------------------ | +| 2026-07-27 | Accepted | Global terminology chosen for current and future APIs. | diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..4036a65 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,49 @@ +# Architecture decision records + +An Architecture Decision Record (ADR) documents an accepted, durable decision +that guides Hyperkernel's implementation and public contracts. An ADR explains +what was decided, why it was chosen, and the consequences that future work must +respect. + +ADRs are not exploratory design documents. A decision can later be replaced, +but it remains accepted until a newer ADR supersedes it. + +## Records + +| Record | Status | +| ---------------------------------------------------------------------------- | -------- | +| [0001: Path and pathname terminology](0001-path-and-pathname-terminology.md) | Accepted | + +## Relationship to design records + +[`../design/`](../design/README.md) contains ideas that progress through +design stages. A design that is fully approved and tested may be promoted to +an ADR when it establishes a durable rule, boundary, or public contract. + +The design record remains as the history and evidence for the decision. The +resulting ADR links to that design record, and the design record links to the +ADR. Not every completed design needs an ADR; create one only when the decision +must guide future work beyond its original implementation. + +An ADR may also be created directly when a small, well-understood decision does +not need a separate design process. + +## Status + +| Status | Meaning | +| ---------- | ------------------------------------------------------------------ | +| Accepted | The decision is active and guides current work. | +| Superseded | A newer ADR replaced the decision; retain this record for history. | + +## Structure + +Each ADR contains: + +1. metadata with its status and decision date; +2. a concise summary and the problem being decided; +3. the decision and examples or rules needed to apply it; +4. considered alternatives and consequences; +5. links to any originating design record or replacement ADR. + +Number ADRs sequentially within this directory. The number is a stable identity +and is not reused. diff --git a/docs/design/0005-local-package-workspaces.md b/docs/design/0005-local-package-workspaces.md new file mode 100644 index 0000000..500f450 --- /dev/null +++ b/docs/design/0005-local-package-workspaces.md @@ -0,0 +1,86 @@ +# Local package workspaces + +| Field | Value | +| ------ | ---------------------------------------------------- | +| Status | Development | +| Scope | Repository architecture and local package boundaries | +| Date | 2026-07-27 | + +## Summary + +Hyperkernel will use npm workspaces to develop the SvelteKit application and +future reusable packages in one repository. Workspace packages are consumed by +their package names locally before any publication to npm. + +## Problem and invariants + +The current repository is one SvelteKit application. It needs an incremental +path toward a reusable kernel and UI packages without duplicating development +tooling or making the kernel depend on SvelteKit. + +- The SvelteKit application remains the executable prototype during this step. +- Formatting, linting, type-checking, and test tooling stay at the repository + root. +- The kernel public API must not depend on SvelteKit routes, UI state, or + server-only SQLite implementation details. +- No package is published or supported for external installation yet. + +## Decision + +The repository root is a private npm workspace coordinator named +`hyperkernel-workspace`. It declares `packages/*` as workspaces. + +The initial workspaces are: + +- `packages/kernel`, named `@hyperkernel/kernel`, whose empty public entry point + establishes the future import boundary without moving any existing kernel + implementation; and +- `packages/sqlite`, named `@hyperkernel/sqlite`, which extracts the existing + Node.js SQLite connection and baseline pragmas from the SvelteKit application. + +Both packages remain private until an explicit publication decision and +packaging verification are complete. + +The existing root SvelteKit application stays in place for now. Moving it under +`apps/` and introducing a UI package are later migrations, not prerequisites +for validating the first workspace boundary. + +## Alternatives considered + +### Publish `src/lib` from the application + +Rejected. `src/lib` is the application's internal library. Publishing it would +make application aliases and accidental SvelteKit dependencies part of a public +package boundary. + +### Move the SvelteKit application and all packages at once + +Rejected. This would combine package resolution, build configuration, test +paths, and code extraction into one hard-to-debug migration. + +### Use separate repositories immediately + +Rejected. Local npm workspaces provide the desired package-name imports while +keeping the prototype changes atomic and easy to inspect. + +## Consequences and limitations + +`@hyperkernel/kernel` is locally addressable but intentionally exports no +production API in this step. `@hyperkernel/sqlite` is Node-only and exposes the +low-level connection API described in +[`0006-sqlite-connection-package.md`](0006-sqlite-connection-package.md). A +later step must select and verify distribution builds before either package can +be published. + +## Evidence required for Evaluation + +- npm installs the workspace successfully from the repository root. +- The root application continues to pass its existing checks. +- The root application resolves both packages through their package names. +- The SQLite connection behavior remains covered after extraction. + +## Status history + +| Date | Status | Note | +| ---------- | ----------- | ------------------------------------------------- | +| 2026-07-27 | Development | Chosen for the initial local workspace migration. | diff --git a/docs/design/0006-sqlite-connection-package.md b/docs/design/0006-sqlite-connection-package.md new file mode 100644 index 0000000..8675b09 --- /dev/null +++ b/docs/design/0006-sqlite-connection-package.md @@ -0,0 +1,84 @@ +# SQLite connection package + +| Field | Value | +| ------ | ----------------------------- | +| Status | Development | +| Scope | SQLite infrastructure adapter | +| Date | 2026-07-27 | + +## Summary + +`@hyperkernel/sqlite` provides the Node.js SQLite connection used by +Hyperkernel applications. Its first public API is `openDatabase`, which opens +a database and returns the underlying `DatabaseSync` instance. + +## Problem and invariants + +The SvelteKit prototype currently owns a single server-only SQLite connection +and its baseline pragmas. That infrastructure must become reusable without +making the kernel depend on SvelteKit or allowing SQLite to be presented as a +database-agnostic contract. + +- The package is Node-only and uses the built-in `node:sqlite` module. +- `openDatabase` opens an existing database or lets SQLite create a missing + file-backed database. +- The caller owns and closes the returned `DatabaseSync` instance. +- The adapter preserves the prototype's timeout, WAL, synchronous, and foreign + key settings. +- Opening `:memory:` warns that data is lost when the process stops. +- This low-level connection API is not a command, event, or authorization API. + +## Decision + +Expose `openDatabase(path = ":memory:", options = {})` from +`@hyperkernel/sqlite`. `path` accepts a filesystem path, `:memory:`, or a URL +accepted by `DatabaseSync`. The optional `options` argument currently accepts +`timeout`. The function returns `DatabaseSync` directly. + +The name is deliberately short because the SQLite-specific package name +supplies the missing context: + +```ts +import { openDatabase } from "@hyperkernel/sqlite"; +``` + +`@hyperkernel/db` is reserved for a future database-agnostic contract only if +multiple adapters establish a real shared abstraction. + +## Alternatives considered + +### `createDatabase` + +Rejected. The operation also opens existing databases, so `create` would imply +an inaccurate lifecycle guarantee. + +### `openSqliteDatabase` + +Rejected. It is correct but redundant at the package boundary. + +### Return a Hyperkernel-specific storage interface + +Deferred. The production kernel persistence contract does not yet exist. A +premature abstraction would hide SQLite behavior before another adapter has +shown which operations genuinely need to be shared. + +## Consequences and limitations + +Consumers receive a raw SQLite connection and can execute arbitrary SQL. This +is appropriate for an infrastructure package but does not grant a supported +path around the future kernel command boundary. The package is private and not +yet prepared for npm publication. + +## Evidence required for Evaluation + +- The SvelteKit server connection uses the package rather than creating its own + `DatabaseSync` instance. +- A test proves that `openDatabase` returns `DatabaseSync` and enables foreign + keys. +- The application type-checks with the workspace dependency. + +## Status history + +| Date | Status | Note | +| ---------- | ----------- | ----------------------------------------------------- | +| 2026-07-27 | Development | Chosen for the first reusable infrastructure adapter. | diff --git a/docs/design/README.md b/docs/design/README.md index 4dd145e..e866499 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -1,14 +1,13 @@ # Design records -Design records document significant Hyperkernel decisions whose reasoning, -implementation maturity, and compatibility consequences must remain reviewable. -They complement the public architecture in `README.md` and the canonical -engineering rules in `AGENTS.md`. +Design records capture Hyperkernel ideas while they are proposed, developed, +and evaluated. They preserve the reasoning, evidence, and unresolved questions +needed to reach a decision. They complement the public architecture in +`README.md` and the canonical engineering rules in `AGENTS.md`. -Use a design record for a kernel contract, a public extension contract, or -another decision whose alternatives and long-term consequences matter. Do not -create one for routine implementation details that are adequately explained by -code and tests. +Use a design record when a significant idea needs deliberate exploration before +it becomes a durable decision. Do not create one for routine implementation +details that are adequately explained by code and tests. ## Records @@ -18,30 +17,37 @@ code and tests. | [0002: Event-sourced persistence with SQLite](0002-event-sourced-persistence-with-sqlite.md) | Development | | [0003: Web-platform-first frontend](0003-web-platform-first-frontend.md) | Development | | [0004: Interface design philosophy](0004-interface-design-philosophy.md) | Development | +| [0005: Local package workspaces](0005-local-package-workspaces.md) | Development | +| [0006: SQLite connection package](0006-sqlite-connection-package.md) | Development | ## Authority -`README.md` and `AGENTS.md` remain canonical. A design record elaborates those -contracts but does not silently override them. - -- Draft, Development, and Evaluation records do not define stable contracts. -- A Stable record may elaborate a canonical contract. -- Once Stable, a record should receive only editorial clarifications. -- A material change requires a new record. The previous record becomes Legacy - and links to its replacement. -- Spikes may provide evidence for a record but never define supported behavior. +`README.md` and `AGENTS.md` remain canonical. A design record does not silently +override them or establish a durable contract. + +- Draft, Development, and Evaluation records are ideas, not accepted decisions. +- A Stable design is fully approved and tested for its intended scope. +- When a Stable design establishes a durable rule, boundary, or public contract, + create an ADR in [`../adr/`](../adr/README.md) to record the accepted + decision. Keep the design record as the history and evidence, with links in + both directions. +- Not every Stable design needs an ADR; routine or local decisions may remain + documented in code and tests. +- A material change to an accepted decision requires a new ADR that supersedes + the earlier one. +- Spikes may provide evidence for a design but never define supported behavior. ## Status Design records advance through these statuses: -| Status | Meaning | -| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| Draft | The problem and proposed decision are under discussion. Nothing is approved for implementation or support. | -| Development | The design is chosen and implementation is underway. The contract may still change incompatibly. | -| Evaluation | The implementation works end to end and is being evaluated through tests and concrete use. The contract is not yet stable. | -| Stable | The contract is supported. Compatibility and replay guarantees apply, and material changes require a new design record. | -| Legacy | Existing use and historical compatibility remain supported, but new use is prohibited. The record must identify its Stable replacement. | +| Status | Meaning | +| ----------- | -------------------------------------------------------------------------------------------------------------------------- | +| Draft | The problem and proposed decision are under discussion. Nothing is approved for implementation or support. | +| Development | The design is chosen and implementation is underway. The contract may still change incompatibly. | +| Evaluation | The implementation works end to end and is being evaluated through tests and concrete use. The contract is not yet stable. | +| Stable | The design is fully approved and tested. Create an ADR if it establishes a durable rule, boundary, or public contract. | +| Legacy | The design no longer guides new work. Retain it as historical context and link to any successor design or ADR. | A Draft that is abandoned before implementation remains in the repository with a prominent note explaining that no decision was adopted. diff --git a/package-lock.json b/package-lock.json index 4ca406b..64489ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,19 @@ { - "name": "@hnordt/hyperkernel", + "name": "hyperkernel-workspace", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@hnordt/hyperkernel", + "name": "hyperkernel-workspace", "version": "0.0.1", "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@fontsource-variable/inter": "^5.3.0", + "@hyperkernel/sqlite": "^0.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -286,6 +290,14 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@hyperkernel/kernel": { + "resolved": "packages/kernel", + "link": true + }, + "node_modules/@hyperkernel/sqlite": { + "resolved": "packages/sqlite", + "link": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -4233,6 +4245,19 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "packages/kernel": { + "name": "@hyperkernel/kernel", + "version": "0.0.1", + "license": "MIT" + }, + "packages/sqlite": { + "name": "@hyperkernel/sqlite", + "version": "0.0.1", + "license": "MIT", + "engines": { + "node": ">=24" + } } } } diff --git a/package.json b/package.json index b367bec..d234b8d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { - "name": "@hnordt/hyperkernel", + "name": "hyperkernel-workspace", "version": "0.0.1", "private": true, + "workspaces": [ + "packages/*" + ], "description": "Open-source primitives for building reliable, auditable, self-hosted software platforms.", "license": "MIT", "repository": { @@ -37,6 +40,7 @@ }, "dependencies": { "@fontsource-variable/inter": "^5.3.0", + "@hyperkernel/sqlite": "^0.0.1", "zod": "^4.4.3" }, "devDependencies": { diff --git a/packages/kernel/package.json b/packages/kernel/package.json new file mode 100644 index 0000000..8734a76 --- /dev/null +++ b/packages/kernel/package.json @@ -0,0 +1,11 @@ +{ + "name": "@hyperkernel/kernel", + "version": "0.0.1", + "private": true, + "description": "Trusted kernel primitives for Hyperkernel applications.", + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts" + } +} diff --git a/packages/kernel/src/index.ts b/packages/kernel/src/index.ts new file mode 100644 index 0000000..8479b0c --- /dev/null +++ b/packages/kernel/src/index.ts @@ -0,0 +1,7 @@ +/** + * Public kernel API. + * + * This package is intentionally empty while the initial workspace boundary is + * verified. Domain contracts will move here in later, separately reviewed steps. + */ +export {}; diff --git a/packages/sqlite/package.json b/packages/sqlite/package.json new file mode 100644 index 0000000..18abbd0 --- /dev/null +++ b/packages/sqlite/package.json @@ -0,0 +1,14 @@ +{ + "name": "@hyperkernel/sqlite", + "version": "0.0.1", + "private": true, + "description": "SQLite connection primitives for Hyperkernel applications.", + "license": "MIT", + "type": "module", + "engines": { + "node": ">=24" + }, + "exports": { + ".": "./src/index.ts" + } +} diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts new file mode 100644 index 0000000..b1a5523 --- /dev/null +++ b/packages/sqlite/src/index.ts @@ -0,0 +1,28 @@ +import { DatabaseSync } from "node:sqlite"; + +/** + * Opens a SQLite database and applies Hyperkernel's baseline connection settings. + * + * The database is created by SQLite when `path` names a file that does not yet + * exist. Callers own the returned connection and must close it when finished. + */ +export function openDatabase( + path: string | URL = ":memory:", + options: { timeout?: number } = {}, +): DatabaseSync { + if (path === ":memory:") { + console.warn( + "Using an in-memory SQLite database. All data will be lost when the process stops.", + ); + } + + const database = new DatabaseSync(path, { + timeout: options.timeout ?? 5000, + }); + + database.exec("PRAGMA journal_mode = WAL"); + database.exec("PRAGMA synchronous = NORMAL"); + database.exec("PRAGMA foreign_keys = ON"); + + return database; +} diff --git a/src/lib/kernel-package.test.ts b/src/lib/kernel-package.test.ts new file mode 100644 index 0000000..17b6df0 --- /dev/null +++ b/src/lib/kernel-package.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from "vitest"; +import * as kernel from "@hyperkernel/kernel"; + +test("resolves the kernel workspace through its package name", () => { + expect(Object.prototype.toString.call(kernel)).toBe("[object Module]"); +}); diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index c627936..3bb3802 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -1,18 +1,4 @@ -import { DatabaseSync } from "node:sqlite"; +import { openDatabase } from "@hyperkernel/sqlite"; import { env } from "$env/dynamic/private"; -const dbUrl = env.DATABASE_URL ?? ":memory:"; - -if (dbUrl === ":memory:") { - console.warn( - "Using an in-memory SQLite database. All data will be lost when the process stops.", - ); -} - -export const db = new DatabaseSync(dbUrl, { - timeout: 5000, -}); - -db.exec("PRAGMA journal_mode = WAL"); -db.exec("PRAGMA synchronous = NORMAL"); -db.exec("PRAGMA foreign_keys = ON"); +export const db = openDatabase(env.DATABASE_URL); diff --git a/src/lib/server/db/open-database.test.ts b/src/lib/server/db/open-database.test.ts new file mode 100644 index 0000000..eddc750 --- /dev/null +++ b/src/lib/server/db/open-database.test.ts @@ -0,0 +1,21 @@ +import { DatabaseSync } from "node:sqlite"; +import { expect, test, vi } from "vitest"; +import { openDatabase } from "@hyperkernel/sqlite"; + +test("opens an in-memory SQLite database by default", () => { + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + const database = openDatabase(undefined); + + try { + expect(database).toBeInstanceOf(DatabaseSync); + expect(warn).toHaveBeenCalledWith( + "Using an in-memory SQLite database. All data will be lost when the process stops.", + ); + expect(database.prepare("PRAGMA foreign_keys").get()).toEqual({ + foreign_keys: 1, + }); + } finally { + database.close(); + warn.mockRestore(); + } +}); From 4afa2213184bc603ca6d45d2ead1fd2cc36bedc3 Mon Sep 17 00:00:00 2001 From: Heliton Nordt <1625399+hnordt@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:46:28 -0300 Subject: [PATCH 02/20] Document SQLite connection defaults and ownership strategy --- packages/sqlite/src/index.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index b1a5523..5a97f09 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -5,10 +5,14 @@ import { DatabaseSync } from "node:sqlite"; * * The database is created by SQLite when `path` names a file that does not yet * exist. Callers own the returned connection and must close it when finished. + * + * @param path SQLite database filename. Defaults to `:memory:` when omitted. + * @param options SQLite connection options. + * @param options.timeout Maximum time, in milliseconds, to wait for a locked database. Defaults to `5000`. */ export function openDatabase( path: string | URL = ":memory:", - options: { timeout?: number } = {}, + options = { timeout: 5000 }, ): DatabaseSync { if (path === ":memory:") { console.warn( @@ -16,9 +20,13 @@ export function openDatabase( ); } - const database = new DatabaseSync(path, { - timeout: options.timeout ?? 5000, - }); + const database = new DatabaseSync(path, { timeout: options.timeout }); + + // TODO: Define single-server database ownership: prevent concurrent Hyperkernel + // instances from sharing a database, preferably through an exclusive lock. This + // protects the running database from external writes but is not a deployment security + // boundary. Reconcile it with native daily snapshots and incremental event export to + // S3; external tools such as Litestream may not access an exclusively locked database. database.exec("PRAGMA journal_mode = WAL"); database.exec("PRAGMA synchronous = NORMAL"); From 0c0cab9eabe8728579444c08060d7a39bbbd0038 Mon Sep 17 00:00:00 2001 From: Heliton Nordt <1625399+hnordt@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:11:28 -0300 Subject: [PATCH 03/20] refactor: move UI components and SQLite tests into packages --- AGENTS.md | 3 +- docs/design/0007-local-package-workspaces.md | 20 +++++++---- package-lock.json | 35 ++++++++----------- package.json | 1 + .../sqlite/src}/open-database.test.ts | 2 +- packages/ui/package.json | 14 ++++++++ .../ui/src}/Canvas.svelte | 0 .../ui/src}/Heading.svelte | 0 .../ui/src}/Icon.svelte | 0 .../ui/src}/Icon.svelte.test.ts | 0 .../ui/src}/Paragraph.svelte | 0 .../ui/src}/Theme.svelte | 0 .../ui/src}/Window.svelte | 2 +- packages/ui/src/index.ts | 6 ++++ src/routes/+layout.svelte | 3 +- src/routes/+page.svelte | 4 +-- tsconfig.json | 22 +++++++++++- vite.config.ts | 9 +++-- 18 files changed, 82 insertions(+), 39 deletions(-) rename {src/lib/server/db => packages/sqlite/src}/open-database.test.ts (92%) create mode 100644 packages/ui/package.json rename {src/lib/components => packages/ui/src}/Canvas.svelte (100%) rename {src/lib/components => packages/ui/src}/Heading.svelte (100%) rename {src/lib/components => packages/ui/src}/Icon.svelte (100%) rename {src/lib/components => packages/ui/src}/Icon.svelte.test.ts (100%) rename {src/lib/components => packages/ui/src}/Paragraph.svelte (100%) rename {src/lib/components => packages/ui/src}/Theme.svelte (100%) rename {src/lib/components => packages/ui/src}/Window.svelte (93%) create mode 100644 packages/ui/src/index.ts diff --git a/AGENTS.md b/AGENTS.md index 6628c7d..4bcba8d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -191,7 +191,8 @@ Hyperkernel currently requires Node.js 24 or later and npm 11 or later. - `src/routes/` contains SvelteKit routes and transport/UI entry points. - `src/lib/server/db/index.ts` owns the shared `DatabaseSync` connection. - `src/lib/server/` is server-only and must never be imported by client code. -- `src/lib/components/` contains reusable interface components. +- `packages/ui/src/` contains reusable interface components exported through + `@hyperkernel/ui`. - `docs/adr/` contains architecture decision records for repository-wide architecture and governance definitions. - `docs/design/` contains numbered design records for significant contracts and decisions. - `docs/spikes/` contains isolated architecture experiments. Spikes are not production modules, supported APIs, or proof that a contract is implemented. diff --git a/docs/design/0007-local-package-workspaces.md b/docs/design/0007-local-package-workspaces.md index 4a8b11d..ce2e72a 100644 --- a/docs/design/0007-local-package-workspaces.md +++ b/docs/design/0007-local-package-workspaces.md @@ -30,7 +30,7 @@ tooling or making the kernel depend on SvelteKit. The repository root is a private npm workspace coordinator named `hyperkernel-workspace`. It declares `packages/*` as workspaces. -The initial workspaces are: +The initial workspaces were: - `packages/kernel`, named `@hyperkernel/kernel`, whose empty public entry point establishes the future import boundary without moving any existing kernel @@ -41,9 +41,14 @@ The initial workspaces are: Both packages remain private until an explicit publication decision and packaging verification are complete. +A subsequent Experience-layer migration adds `packages/ui`, named +`@hyperkernel/ui`. It owns the reusable Svelte components previously kept in +the root application's `src/lib/components` directory and exposes them through +one package entry point. + The existing root SvelteKit application stays in place for now. Moving it under -`apps/` and introducing a UI package are later migrations, not prerequisites -for validating the first workspace boundary. +`apps/` remains a later migration and is not a prerequisite for validating the +workspace boundaries. ## Alternatives considered @@ -68,15 +73,17 @@ keeping the prototype changes atomic and easy to inspect. `@hyperkernel/kernel` is locally addressable but intentionally exports no production API in this step. `@hyperkernel/sqlite` is Node-only and exposes the low-level connection API described in -[`0008-sqlite-connection-package.md`](0008-sqlite-connection-package.md). A -later step must select and verify distribution builds before either package can +[`0008-sqlite-connection-package.md`](0008-sqlite-connection-package.md). +`@hyperkernel/ui` is consumed directly from source by the root application. +A later step must select and verify distribution builds before any package can be published. ## Evidence required for Evaluation - npm installs the workspace successfully from the repository root. - The root application continues to pass its existing checks. -- The root application resolves both packages through their package names. +- The root application resolves its workspace dependencies through their + package names. - The SQLite connection behavior remains covered after extraction. ## Status history @@ -84,3 +91,4 @@ be published. | Date | Status | Note | | ---------- | ----------- | ------------------------------------------------- | | 2026-07-27 | Development | Chosen for the initial local workspace migration. | +| 2026-07-28 | Development | Added the local UI component package boundary. | diff --git a/package-lock.json b/package-lock.json index eb2c00b..19f5906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "dependencies": { "@fontsource-variable/inter": "^5.3.0", "@hyperkernel/sqlite": "^0.0.1", + "@hyperkernel/ui": "^0.0.1", "zod": "^4.4.3" }, "devDependencies": { @@ -298,11 +299,14 @@ "resolved": "packages/sqlite", "link": true }, + "node_modules/@hyperkernel/ui": { + "resolved": "packages/ui", + "link": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -313,7 +317,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -324,7 +327,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -334,14 +336,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1200,7 +1200,6 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz", "integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -1355,7 +1354,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1386,7 +1384,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -1539,7 +1536,7 @@ "version": "8.65.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1793,7 +1790,6 @@ "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1833,7 +1829,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1853,7 +1848,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1912,7 +1906,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2019,7 +2012,6 @@ "version": "5.8.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", - "dev": true, "license": "MIT" }, "node_modules/es-errors": { @@ -2210,7 +2202,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, "license": "MIT" }, "node_modules/espree": { @@ -2248,7 +2239,6 @@ "version": "2.2.13", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.13.tgz", "integrity": "sha512-m8jH5hZgJE2RRUK/jjkGPcJEDAV+dYnZYFkosQaPTcE+Yw4xynXHOo6FUdwaWBtdR3b1MMa7wEDTSHeR2VWsGA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -2938,7 +2928,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -2961,7 +2950,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -3627,7 +3615,6 @@ "version": "5.56.8", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.8.tgz", "integrity": "sha512-PY8LOw7xP6c8IOiVqdo0sbbZVYhXRSfklOQLAUyGBKqjTX0wx/z4l/9J+PmBpmlLnxzEb1NqltxQ5/wZme/Cmg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -3759,7 +3746,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -4234,7 +4220,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, "license": "MIT" }, "node_modules/zod": { @@ -4258,6 +4243,14 @@ "engines": { "node": ">=24" } + }, + "packages/ui": { + "name": "@hyperkernel/ui", + "version": "0.0.1", + "license": "MIT", + "peerDependencies": { + "svelte": "^5.56.8" + } } } } diff --git a/package.json b/package.json index e62db3b..7ae4fb2 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "@fontsource-variable/inter": "^5.3.0", "@hyperkernel/sqlite": "^0.0.1", + "@hyperkernel/ui": "^0.0.1", "zod": "^4.4.3" }, "devDependencies": { diff --git a/src/lib/server/db/open-database.test.ts b/packages/sqlite/src/open-database.test.ts similarity index 92% rename from src/lib/server/db/open-database.test.ts rename to packages/sqlite/src/open-database.test.ts index eddc750..950048c 100644 --- a/src/lib/server/db/open-database.test.ts +++ b/packages/sqlite/src/open-database.test.ts @@ -1,6 +1,6 @@ import { DatabaseSync } from "node:sqlite"; import { expect, test, vi } from "vitest"; -import { openDatabase } from "@hyperkernel/sqlite"; +import { openDatabase } from "./index.js"; test("opens an in-memory SQLite database by default", () => { const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..d8abdfc --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,14 @@ +{ + "name": "@hyperkernel/ui", + "version": "0.0.1", + "private": true, + "description": "Reusable interface components for Hyperkernel applications.", + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "peerDependencies": { + "svelte": "^5.56.8" + } +} diff --git a/src/lib/components/Canvas.svelte b/packages/ui/src/Canvas.svelte similarity index 100% rename from src/lib/components/Canvas.svelte rename to packages/ui/src/Canvas.svelte diff --git a/src/lib/components/Heading.svelte b/packages/ui/src/Heading.svelte similarity index 100% rename from src/lib/components/Heading.svelte rename to packages/ui/src/Heading.svelte diff --git a/src/lib/components/Icon.svelte b/packages/ui/src/Icon.svelte similarity index 100% rename from src/lib/components/Icon.svelte rename to packages/ui/src/Icon.svelte diff --git a/src/lib/components/Icon.svelte.test.ts b/packages/ui/src/Icon.svelte.test.ts similarity index 100% rename from src/lib/components/Icon.svelte.test.ts rename to packages/ui/src/Icon.svelte.test.ts diff --git a/src/lib/components/Paragraph.svelte b/packages/ui/src/Paragraph.svelte similarity index 100% rename from src/lib/components/Paragraph.svelte rename to packages/ui/src/Paragraph.svelte diff --git a/src/lib/components/Theme.svelte b/packages/ui/src/Theme.svelte similarity index 100% rename from src/lib/components/Theme.svelte rename to packages/ui/src/Theme.svelte diff --git a/src/lib/components/Window.svelte b/packages/ui/src/Window.svelte similarity index 93% rename from src/lib/components/Window.svelte rename to packages/ui/src/Window.svelte index 14f8d1f..b1e8d7e 100644 --- a/src/lib/components/Window.svelte +++ b/packages/ui/src/Window.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index b5b2508..3ac3314 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,5 @@ diff --git a/tsconfig.json b/tsconfig.json index 8079ee2..0cd9f00 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,25 @@ "skipLibCheck": true, "esModuleInterop": true, "rewriteRelativeImportExtensions": true - } + }, + "include": [ + ".svelte-kit/ambient.d.ts", + ".svelte-kit/env.d.ts", + ".svelte-kit/non-ambient.d.ts", + ".svelte-kit/types/**/$types.d.ts", + "vite.config.js", + "vite.config.ts", + "src/**/*.js", + "src/**/*.ts", + "src/**/*.svelte", + "packages/**/*.js", + "packages/**/*.ts", + "packages/**/*.svelte", + "test/**/*.js", + "test/**/*.ts", + "test/**/*.svelte", + "tests/**/*.js", + "tests/**/*.ts", + "tests/**/*.svelte" + ] } diff --git a/vite.config.ts b/vite.config.ts index 1d90a70..50baa7f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -36,7 +36,10 @@ export default defineConfig({ ], enabled: true, }, - include: ["src/**/*.svelte.{test,spec}.ts"], + include: [ + "src/**/*.svelte.{test,spec}.ts", + "packages/ui/src/**/*.svelte.{test,spec}.ts", + ], exclude: ["src/lib/server/**"], }, }, @@ -45,8 +48,8 @@ export default defineConfig({ test: { name: "server", environment: "node", - include: ["src/**/*.{test,spec}.ts"], - exclude: ["src/**/*.svelte.{test,spec}.ts"], + include: ["src/**/*.{test,spec}.ts", "packages/**/*.{test,spec}.ts"], + exclude: ["**/*.svelte.{test,spec}.ts"], }, }, ], From b354dc2c33f9d5131d6887d2b907c4b8d1b5cfa8 Mon Sep 17 00:00:00 2001 From: Heliton Nordt <1625399+hnordt@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:27:09 -0300 Subject: [PATCH 04/20] Move the playground application into its workspace --- .gitignore | 10 +++-- AGENTS.md | 25 ++++++++--- CONTRIBUTING.md | 6 +-- README.md | 19 ++++++-- .env.example => apps/playground/.env.example | 0 apps/playground/package.json | 43 +++++++++++++++++++ .../playground/playwright.config.ts | 0 {src => apps/playground/src}/app.d.ts | 0 {src => apps/playground/src}/app.html | 0 .../playground/src}/lib/assets/favicon.svg | 0 .../src}/lib/kernel-package.test.ts | 0 .../playground/src}/lib/server/db/index.ts | 0 .../playground/src}/routes/+layout.svelte | 0 .../playground/src}/routes/+page.svelte | 0 {tests => apps/playground/tests}/home.e2e.ts | 0 apps/playground/tsconfig.json | 30 +++++++++++++ .../playground/vite.config.ts | 12 ++++-- docs/design/0007-local-package-workspaces.md | 34 ++++++++------- docs/design/0008-sqlite-connection-package.md | 8 ++-- docs/spikes/README.md | 8 ++-- docs/spikes/event-sourcing.ts | 4 +- package-lock.json | 41 +++++++++++++++--- package.json | 23 ++++------ tsconfig.json | 22 +--------- 24 files changed, 199 insertions(+), 86 deletions(-) rename .env.example => apps/playground/.env.example (100%) create mode 100644 apps/playground/package.json rename playwright.config.ts => apps/playground/playwright.config.ts (100%) rename {src => apps/playground/src}/app.d.ts (100%) rename {src => apps/playground/src}/app.html (100%) rename {src => apps/playground/src}/lib/assets/favicon.svg (100%) rename {src => apps/playground/src}/lib/kernel-package.test.ts (100%) rename {src => apps/playground/src}/lib/server/db/index.ts (100%) rename {src => apps/playground/src}/routes/+layout.svelte (100%) rename {src => apps/playground/src}/routes/+page.svelte (100%) rename {tests => apps/playground/tests}/home.e2e.ts (100%) create mode 100644 apps/playground/tsconfig.json rename vite.config.ts => apps/playground/vite.config.ts (80%) diff --git a/.gitignore b/.gitignore index ec4fd81..e978194 100644 --- a/.gitignore +++ b/.gitignore @@ -11,12 +11,14 @@ *.db # Node.js -/node_modules +node_modules # SvelteKit -/.svelte-kit -/build +/apps/*/.svelte-kit +/apps/*/build # Playwright /.playwright-mcp -/test-results +/apps/*/.playwright-mcp +/apps/*/playwright-report +/apps/*/test-results diff --git a/AGENTS.md b/AGENTS.md index 4bcba8d..77eddfe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,26 +188,39 @@ Hyperkernel currently requires Node.js 24 or later and npm 11 or later. ## Repository boundaries -- `src/routes/` contains SvelteKit routes and transport/UI entry points. -- `src/lib/server/db/index.ts` owns the shared `DatabaseSync` connection. -- `src/lib/server/` is server-only and must never be imported by client code. +- `apps/playground/` contains the private example application that developers + use to learn and experiment with Hyperkernel. +- `apps/playground/src/routes/` contains SvelteKit routes and transport/UI entry + points. +- `apps/playground/src/lib/server/db/index.ts` owns the shared `DatabaseSync` + connection for the playground. +- `apps/playground/src/lib/server/` is server-only and must never be imported by + client code. - `packages/ui/src/` contains reusable interface components exported through `@hyperkernel/ui`. - `docs/adr/` contains architecture decision records for repository-wide architecture and governance definitions. - `docs/design/` contains numbered design records for significant contracts and decisions. - `docs/spikes/` contains isolated architecture experiments. Spikes are not production modules, supported APIs, or proof that a contract is implemented. -Keep database connection creation centralized in `src/lib/server/db/index.ts`. Use the built-in `node:sqlite` module. Do not introduce an ORM, another SQLite client, a second ad hoc connection, or direct database access outside the server boundary unless an intentional architecture change is documented and approved. +Keep database connection creation centralized in +`apps/playground/src/lib/server/db/index.ts`. Use the built-in `node:sqlite` +module. Do not introduce an ORM, another SQLite client, a second ad hoc +connection, or direct database access outside the server boundary unless an +intentional architecture change is documented and approved. Framework transports must be adapters around the command/query contracts. Do not make the kernel depend on SvelteKit request objects, remote-function internals, or UI component state. ## Svelte and interface guidance - Use Svelte 5 runes for application code. -- Experimental SvelteKit remote-function support is enabled in `vite.config.ts`; it may be used for RPC-style flows, but authoritative command, event, and projection contracts must remain framework-independent. +- Experimental SvelteKit remote-function support is enabled in + `apps/playground/vite.config.ts`; it may be used for RPC-style flows, but + authoritative command, event, and projection contracts must remain + framework-independent. - Use native CSS in scoped Svelte `