This page walks you through a new kind of record, from the database to the screen, by copying notes. At the end, people in a group can list, add, edit and delete it, live on every open tab.
Seed calls such a kind of record an aggregate: a note, an invoice, a task. Every aggregate has
the same files, so you copy the notes files and rename them. This page uses projects as the new
name; put yours in its place.
You need:
- The stack running (Run the local stack).
deno task checkpassing before you start, so a failure later is yours.
Each step names the files to copy. The aggregate reference says what each file holds, in the same order and with the same step numbers.
- Copy
libs/domain/notes/tolibs/domain/projects/. RenameNotetoProjectinside, and change the fields and their rules. - In the root
deno.jsonc, add./libs/domain/projectstoworkspaceand an@domain/projectsimport alias. Without the workspace entry, its tests never run. - In
Dockerfile.base, add aCOPYline for itsdeno.json, next to the notes line.
Check it:
deno test --no-prompt --allow-read=. libs/domain/projects tests/dockerfile-base.test.ts
Copy the notes migration,
libs/server/db/migrations/2026_10_02_0001_notes.sql. Name the copy one step past the newest file in that folder (Naming a migration).Append the same table to
libs/server/db/schema.sql.Append the file's name as the last line of
ON_MAINintests/migration-names.test.ts.Restart the stack, which applies the migration:
deno task dev
Check it:
deno test --no-prompt --allow-read=. tests/migration-names.test.ts
- Copy
libs/server/notes/postgres-note-repository.tsandnote-list-cursor.ts, with itsdeno.json, tolibs/server/projects/. - Register it like the domain package:
workspace, an@server/projects/alias and aCOPYline. - Add a
projectgetter toAppDbBaseinapps/api/services/db-base.ts, built per access likenote.
- Copy
apps/api/features/notes/handlers.tstoapps/api/features/projects/. - Copy
apps/api/cqrs/note-dependencies.tsand the note files undercqrs/command-handlers/andcqrs/query-handlers/. - Register the handlers in
apps/api/cqrs/+init.ts.
- Copy
list.ts,operations.ts,errors.tsandtransports.test.tsfromapps/api/features/notes/. - Copy
apps/api/routes/notes.ts, keeping only its list (GET /) route. Every write goes through the operations. - Mount the route in
apps/api/index.ts, before/groups. - Spread your operations into
operationsinapps/api/services/realtimeHub.ts. - Map your domain error in
toRequestError(apps/api/services/realtime.ts).
Check it:
deno test --no-prompt --allow-env=WS_NO_BUFFER_UTIL,WS_NO_UTF_8_VALIDATE apps/api/features/projects
- Copy
libs/ui/notes-screen.tsx(the list page) andnote-editor-screen.tsx(the editor page). - Add the pages' paths to
libs/ui/progressive.tsx, asNOTE_PATHSdoes. - Copy the notes' interaction tests from
libs/ui/notes.test.tsx.
- Copy the store,
apps/spa/src/state/notes.ts, with its test. - Copy
views/NotesView.tsxandviews/NoteEditorView.tsx. - Add the routes to
views/spa-paths.ts, and say what each draws inPAGESinapp.tsx. - Add the list to the icon rail, next to the notes entry in
libs/ui/frame.tsx. Its label is a key in everyi18n/app/*.ts.
Check it:
deno test --no-prompt --allow-env=WS_NO_BUFFER_UTIL,WS_NO_UTF_8_VALIDATE libs/ui apps/spa/src/state
Then open http://app.localhost/projects in two tabs. A project you add in one shows in the other without a reload.
Copy
e2e/notes.e2e.tsand run it (Run the browser tests).Give the aggregate its admin module (Admin modules).
Add fake rows to the public demo (Show it in the public demo).
If it stores something new about a person, update the privacy page,
apps/mpa/routes/[[lang]]/privacy.tsx.Run every check:
deno task check
- The aggregate reference: every file, and the rules it follows.
- Make an aggregate work offline
- Delete, move and export an aggregate