* fix(newsfeed): tighten card spacing and reposition pinned icon - Move pinned icon to top-left corner with a tilted rotation - Remove the extra pin row that added excess top padding - Reduce spacing between post content, reactions, and comments - Remove card box shadow Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(newsfeed): place pinned icon on card corner instead of avatar Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(newsfeed): stop dropdown menus locking body scroll bits-ui defaults DropdownMenu.Content's preventScroll to true, so every open ran a body scroll lock: a forced full-document style recalc and reflow, a horizontal shift by the scrollbar width, and pointer-events: none on the body. On the newsfeed page — a long list of cards each with a scrollable comment thread — that made the comment ellipsis menu feel sluggish to open. Menus don't need scroll locking; only modal layers do. Also tighten card spacing, use the primary color for the pinned icon, give the card menu trigger a pointer cursor, and switch the comment save and submit buttons to the loading and secondary variants. * refactor(newsfeed): store reply targets as data, not markup Replies used to prepend a presentational <blockquote> — Tailwind classes, hardcoded English and a copied snippet of the parent — onto the comment body, and that whole string was persisted to course_newsfeed_comment.content. Editing a reply therefore showed raw markup in the textarea, the quoted snippet froze at write time, the label could never be localized, and two of its ui: utilities never compiled because they appeared only in dashboard code. Add a replyToCommentId column, distinct from parentId (the thread root), so a reply can name the comment it answers. The service verifies the target belongs to the same feed and the same thread; the paginated query joins it to return replyToAuthorFullname, so the label is rendered from live data by the new CommentTree.ReplyingTo component using the existing replying_to key. Rows written the old way still display their stored quote, and stripLegacyReplyQuote keeps that markup out of the edit textarea. * refactor(newsfeed): drop redundant comments on reply target plumbing * fix(newsfeed): address review feedback on pinned icon and quote stripping - The pin icon carried an unprefixed `text-muted-foreground` plus `ui:stroke-primary`, which is emitted nowhere because it appears only in dashboard code, so the outline fell back to the muted colour. Use `ui:text-primary` and let the stroke inherit currentColor. - Give the pinned state an accessible name: the icon is `aria-hidden`, and header.svelte no longer carries an indicator, so screen readers had nothing. - Filter feeds on `isPinned` alone. `is_pinned` is only the database column name; drizzle maps it to `isPinned` and the API never returns the snake_case form, so the `as any` fallback was dead and let the page and card disagree. - `ui:transition-color` is not a Tailwind utility and emitted nothing; the plural `ui:transition-colors` is what applies the transition. - Strip a legacy reply quote only when the blockquote carries the `reply-quote` class the old code always wrote, so a blockquote the author typed is kept. * docs(agents): forbid presentational markup in persisted content columns ---------
@cio/db
This package handles everything related to the database, schema, migrations and connection.
Stack
- Postgres - We use postgres for the database
- Drizzle ORM - For managing and querying the database
- Better Auth - For managing the auth tables (users, account, session).
Workflow
We used Supabase in the past for managing the supabase but have now moved on to use postgres directly and auth managed by our backend. This means the workflow is a mix of the Database First approach (Option 1) and the Codebase First approach (Option 2). Learn more about each approach from the drizzle migration docs.
Here is how I do it:
1. Local DB to Drizzle Schema TS file
I have a postgres tool where I can make changes to the db as I want. I am not so familiar with drizzle that much so I am sticking with this for now.
```bash
pnpm db pull # this does sql -> drizzle ts code
```
2. Drizzle Schema file to Prod SQL
Now when I am ready to deploy the feature, I can use option 2 which is makes the typescript schema the source of truth. This command will only run on CI on every merge on a condition that there was a change to the schema file packages/db/src/schema.ts
```bash
pnpm db push # this does drizzle ts code to prod db
```
OR
I can make changes directly to the schema file and run pnpm db push to push the changes to the source db.
Setting up a new DB on Railway
Once you setup a Postgres DB on Railway, you should copyt the DATABASE_PUBLIC_URL and paste into your .env. In the .env we expect DATABASE_URL:
```.env
DATABASE_URL=what-you-copied-from-railway
```
Commands
db:setup
Prepares the database so the application can run. Run this for new databases or after provisioning Postgres.
pnpm db:setup
It performs three steps in order:
- Schema sync – Runs
drizzle-kit pushto apply the TypeScript schema to the database (creates/updates tables, enums, etc.). - PostgreSQL roles – Creates the
authenticatedandanonroles if missing (used by RLS policies). - Essential seed data – Inserts required reference data: roles (ADMIN, TUTOR, STUDENT), submission statuses, and question types.
Optional: Add --seed (or -s) to run the full seed, including demo users, organizations, courses, and other example data.
Requires DATABASE_URL or PRIVATE_DATABASE_URL in the environment. The scripts load .env from this package directory, so bootstrap it once from the template:
cp .env.example .env # then set DATABASE_URL for your DB
db:backfill-profile-email-verification
Repairs stale profile.is_email_verified rows where the auth user is already verified (user.email_verified = true) or has a linked OAuth/SSO account, but the profile flag was never synced. Use after the auth/profile verification mismatch fix, or when users are blocked by the verify-email modal while send-verification-email returns EMAIL_IS_ALREADY_VERIFIED.
Dry run (lists affected count and a sample; makes no changes):
pnpm db:backfill-profile-email-verification
# or from repo root:
pnpm --filter @cio/db db:backfill-profile-email-verification
Apply updates:
pnpm db:backfill-profile-email-verification -- --execute
The script prints how many profiles were updated and verifies zero mismatches remain.
Seeding
The seed script populates the database with demo data for local development. It is idempotent — running it multiple times is safe; existing records are skipped.
Run all seeds
pnpm seed
# or explicitly
pnpm seed --all
Run individual seeds
Pass one or more flags to run only specific steps:
pnpm seed --users --profiles
pnpm seed --organizations --organization-members --organization-plan
| Flag | What it seeds |
|---|---|
--roles |
ADMIN, TUTOR, STUDENT roles |
--submissions |
Submission statuses |
--question-types |
Question types |
--users |
Auth users (from users.json) |
--profiles |
User profiles |
--accounts |
User accounts |
--organizations |
Demo organizations |
--organization-members |
Org membership records |
--organization-plan |
Organization plan records (enterprise + early adopter) |
--groups |
Course groups |
--group-members |
Group membership records |
--courses |
Demo courses |
--sections |
Course sections |
--lessons |
Lessons |
--exercises |
Exercises |
--questions |
Questions |
--templates |
Exercise templates |
Demo accounts
| Password | Role | Plan | |
|---|---|---|---|
admin@test.com |
123456 |
Admin | Basic (free) |
student@test.com |
123456 |
Student | Basic (free) |
enterprise@test.com |
123456 |
Admin | Enterprise |
enterprise-student@test.com |
123456 |
Student | Enterprise |
early-adopter@test.com |
123456 |
Admin | Early Adopter |
early-adopter-student@test.com |
123456 |
Student | Early Adopter |
Mock user credentials are defined in users.json. The demo accounts above use the password 123456.