A mosaic of personal applications—distinct tools forming one connected suite. https://faceguy.dev
  • TypeScript 96.2%
  • CSS 2.8%
  • Dockerfile 0.9%
  • JavaScript 0.1%
Find a file
renovatebot 875bf61382
All checks were successful
Server CI / lint-and-build (pull_request) Successful in 2m3s
Portfolio CI / lint-and-build (pull_request) Successful in 2m5s
Web CI / lint-and-build (pull_request) Successful in 2m41s
Server CI / build-and-publish (pull_request) Successful in 3m53s
Portfolio CI / build-and-publish (pull_request) Successful in 1m56s
Web CI / build-and-publish (pull_request) Successful in 2m0s
Server CI / lint-and-build (push) Successful in 2m7s
Portfolio CI / lint-and-build (push) Successful in 2m8s
Web CI / lint-and-build (push) Successful in 2m53s
Portfolio CI / build-and-publish (push) Successful in 2m51s
Web CI / build-and-publish (push) Successful in 3m50s
Server CI / build-and-publish (push) Successful in 4m15s
chore(deps): update all non-major dependencies
2026-08-14 19:08:06 +00:00
.agents/skills initial commit 2026-08-02 14:16:46 +03:00
.forgejo/workflows feat: add porfolio website to repo 2026-08-14 18:48:18 +00:00
.zed initial commit 2026-08-02 14:16:46 +03:00
apps feat: add porfolio website to repo 2026-08-14 18:48:18 +00:00
packages feat: add porfolio website to repo 2026-08-14 18:48:18 +00:00
.dockerignore initial commit 2026-08-02 14:16:46 +03:00
.gitattributes initial commit 2026-08-02 14:16:46 +03:00
.gitignore initial commit 2026-08-02 14:16:46 +03:00
.oxfmtrc.json chore: add ci/cd pipeline 2026-08-02 15:26:00 +00:00
.oxlintrc.json initial commit 2026-08-02 14:16:46 +03:00
AGENTS.md feat: add porfolio website to repo 2026-08-14 18:48:18 +00:00
bts.jsonc initial commit 2026-08-02 14:16:46 +03:00
docker-compose.yml feat(web): configure server URL at runtime (#9) 2026-08-04 18:57:13 +00:00
package.json chore(deps): update all non-major dependencies 2026-08-11 23:11:43 +00:00
pnpm-lock.yaml chore(deps): update all non-major dependencies 2026-08-14 19:08:06 +00:00
pnpm-workspace.yaml chore(deps): update all non-major dependencies 2026-08-14 19:08:06 +00:00
README.md feat: add porfolio website to repo 2026-08-14 18:48:18 +00:00
renovate.json chore(renovate): switch assignees to reviewers 2026-08-04 23:09:01 +03:00
skills-lock.json initial commit 2026-08-02 14:16:46 +03:00
tsconfig.json initial commit 2026-08-02 14:16:46 +03:00
turbo.json feat(web): configure server URL at runtime (#9) 2026-08-04 18:57:13 +00:00

Mosaic

Mosaic is a self-hosted, authenticated start page for organizing bookmarks into spaces and categories. It includes a searchable bookmark dashboard, favorites, per-user settings, and email/password authentication. The monorepo also contains a standalone personal portfolio site.

Tech stack

  • Web: Next.js 16, React 19, Tailwind CSS, shadcn/ui, TanStack Query/Form
  • Portfolio: Standalone Next.js 16 portfolio site sharing UI components and theme support
  • API: Hono with type-safe oRPC and generated OpenAPI reference
  • Data: Drizzle ORM with SQLite or Turso/libSQL
  • Authentication: Better Auth (email and password)
  • Tooling: TypeScript, pnpm 10, Turborepo, Oxlint, and Oxfmt
  • Deployment: Docker Compose, with Forgejo CI publishing separate web and server images

Prerequisites

  • Node.js 24 (the Docker images use Node 24)
  • pnpm 10.26.0 (Corepack is recommended)
  • A SQLite/libSQL database, locally or through Turso

Getting started

Install dependencies from the repository root:

corepack enable
pnpm install

Configure environment variables

Create apps/server/.env:

# `pnpm run db:local` serves a local database on port 8080.
# A remote Turso/libSQL URL works here as well.
DATABASE_URL=http://127.0.0.1:8080

# Generate a unique secret with at least 32 characters.
BETTER_AUTH_SECRET=replace-with-a-secret-of-at-least-32-characters
BETTER_AUTH_URL=http://localhost:3000
CORS_ORIGIN=http://localhost:3001
NODE_ENV=development

Create apps/web/.env:

SERVER_URL=http://localhost:3000
# Set to true or 1 to hide registration.
DISABLE_REGISTRATION=false

BETTER_AUTH_URL, CORS_ORIGIN, and SERVER_URL must use the URLs that browsers use to access the server and web app. Use HTTPS URLs when deploying; Better Auth sessions are configured as secure cookies.

SERVER_URL is validated server-side and intentionally passed through the root client provider for browser API calls. When running with Docker, configure it with an API origin that is reachable by both the web container and users' browsers.

Initialize the database

For a local Turso development database, start the local service in one terminal:

pnpm run db:local

Then apply the schema:

pnpm run db:push

For a remote Turso/libSQL database, set its URL as DATABASE_URL and run the same db:push command. Use migrations rather than schema push for managed database changes:

pnpm run db:generate
pnpm run db:migrate

Start development servers

pnpm run dev

The web app is available at http://localhost:3001, the API is available at http://localhost:3000, and the portfolio is available at http://localhost:3002. The OpenAPI reference is served under /api-reference.

To run only the portfolio:

pnpm --filter portfolio dev

Application features

  • Create and switch between bookmark spaces
  • Add, organize, and favorite bookmarks by category
  • Search saved bookmarks or send queries to a configurable search engine
  • Manage a preferred search-query pattern per user
  • Register and sign in with email and password
  • Choose between light and dark themes

Project structure

mosaic/
├── apps/
│   ├── web/                 # Authenticated Next.js start-page application
│   ├── portfolio/           # Standalone Next.js portfolio site
│   └── server/              # Hono HTTP API and oRPC/OpenAPI handlers
├── packages/
│   ├── api/                 # oRPC routers and application logic
│   ├── auth/                # Better Auth configuration
│   ├── config/              # Shared TypeScript configuration
│   ├── db/                  # Drizzle schema, migrations, and database client
│   ├── env/                 # Typed server and web environment validation
│   └── ui/                  # Shared shadcn/ui primitives, hooks, and styles
├── docker-compose.yml
└── turbo.json

Common commands

Command Description
pnpm run dev Start the web app and API in development mode.
pnpm run dev:web Start only the Next.js app on port 3001.
pnpm run dev:server Start only the Hono API on port 3000.
pnpm --filter portfolio dev Start only the portfolio site on port 3002.
pnpm run build Build all applications.
pnpm run check-types Check TypeScript types across the workspace.
pnpm run check Run Oxlint and format files with Oxfmt.
pnpm run db:local Start the local Turso development database.
pnpm run db:push Apply the current Drizzle schema directly to the database.
pnpm run db:generate Generate a Drizzle migration.
pnpm run db:migrate Apply generated Drizzle migrations.
pnpm run db:studio Open Drizzle Studio.

Shared UI components

Reusable UI components live in packages/ui. Update global design tokens in packages/ui/src/styles/globals.css and import components through the package's public exports. Basic shadcn/ui primitives are under components/basic; reusable visual effects and theme components are under components/special and components/theme, respectively:

import { Button } from "@mosaic/ui/components/basic/button";

Add a shared primitive from the repository root:

pnpm dlx shadcn@latest add accordion dialog popover sheet table -c packages/ui

For app-specific blocks, run the shadcn CLI from the consuming app, such as apps/web or apps/portfolio.

Docker Compose deployment

Docker Compose builds and runs separate web and server containers:

pnpm run docker:build
pnpm run docker:up
pnpm run docker:logs
pnpm run docker:down

Compose exposes the web app on port 3001 and the API on port 3000. It optionally loads apps/web/.env and apps/server/.env; the web container uses the server service for server-side requests while browsers use the public API URL baked into the web image. The server bind-mounts local.db at /app/local.db, so SQLite data persists across container recreation; initialize it with pnpm run db:push before the first start. Provide production database and Better Auth values through apps/server/.env, and build the web image with the public API URL that browsers will use.

Continuous integration

Forgejo workflows run linting, formatting checks, focused type checks, and builds for the web/server applications and the portfolio. On pushes to main and version tags, they build and publish mosaic-web, mosaic-server, and mosaic-portfolio images to the configured Forgejo registry. The portfolio image is built from apps/portfolio/Dockerfile for linux/amd64 and is tagged with the short commit SHA, latest on main, and the version tag for releases.