feat(#1): Set up monorepo scaffold with pnpm workspaces + TurboRepo

Implements the foundational project structure including:
- pnpm workspaces configuration
- TurboRepo for build orchestration
- NestJS 11.1.12 API (apps/api)
- Next.js 16.1.6 web app (apps/web)
- Shared packages (config, shared, ui)
- TypeScript strict mode configuration
- ESLint + Prettier setup
- Vitest for unit testing (19 passing tests)

Fixes #1

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-01-28 13:31:33 -06:00
commit 92e20b1686
109 changed files with 8320 additions and 0 deletions

17
.env.example Normal file
View File

@@ -0,0 +1,17 @@
# API Configuration
API_PORT=3001
API_HOST=0.0.0.0
# Web Configuration
NEXT_PUBLIC_API_URL=http://localhost:3001
# Database (configured in later milestone)
# DATABASE_URL=postgresql://user:password@localhost:5432/mosaic
# Authentication (configured in later milestone)
# OIDC_ISSUER=https://auth.example.com
# OIDC_CLIENT_ID=your-client-id
# OIDC_CLIENT_SECRET=your-client-secret
# Development
NODE_ENV=development

38
.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
# Dependencies
node_modules
.pnpm-store
# Build outputs
dist
.next
.turbo
# IDE
.idea
.vscode
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Environment
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Testing
coverage
# Logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
*.tsbuildinfo

6
.prettierignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules
dist
.next
.turbo
coverage
pnpm-lock.yaml

3
.prettierrc.js Normal file
View File

@@ -0,0 +1,3 @@
import config from "@mosaic/config/prettier";
export default config;

313
CLAUDE.md Normal file
View File

@@ -0,0 +1,313 @@
**Multi-tenant personal assistant platform with PostgreSQL backend, Authentik SSO, and MoltBot
integration.**
## Project Overview
Mosaic Stack is a standalone platform that provides:
- Multi-user workspaces with team sharing
- Task, event, and project management
- Gantt charts and Kanban boards
- MoltBot integration via plugins (stock MoltBot + mosaic-plugin-*)
- PDA-friendly design throughout
**Repository:** git.mosaicstack.dev/mosaic/stack
**Versioning:** Start at 0.0.1, MVP = 0.1.0
## Technology Stack
| Layer | Technology |
|-------|------------|
| Frontend | Next.js 16 + React + TailwindCSS + Shadcn/ui |
| Backend | NestJS + Prisma ORM |
| Database | PostgreSQL 17 + pgvector |
| Cache | Valkey (Redis-compatible) |
| Auth | Authentik (OIDC) |
| AI | Ollama (configurable: local or remote) |
| Messaging | MoltBot (stock + Mosaic plugins) |
| Real-time | WebSockets (Socket.io) |
| Monorepo | pnpm workspaces + TurboRepo |
| Testing | Vitest + Playwright |
| Deployment | Docker + docker-compose |
## Repository Structure
mosaic-stack/
├── apps/
│ ├── api/ # mosaic-api (NestJS)
│ │ ├── src/
│ │ │ ├── auth/ # Authentik OIDC
│ │ │ ├── tasks/ # Task management
│ │ │ ├── events/ # Calendar/events
│ │ │ ├── projects/ # Project management
│ │ │ ├── brain/ # MoltBot integration
│ │ │ └── activity/ # Activity logging
│ │ ├── prisma/
│ │ │ └── schema.prisma
│ │ └── Dockerfile
│ └── web/ # mosaic-web (Next.js 16)
│ ├── app/
│ ├── components/
│ └── Dockerfile
├── packages/
│ ├── shared/ # Shared types, utilities
│ ├── ui/ # Shared UI components
│ └── config/ # Shared configuration
├── plugins/
│ ├── mosaic-plugin-brain/ # MoltBot skill: API queries
│ ├── mosaic-plugin-calendar/ # MoltBot skill: Calendar
│ ├── mosaic-plugin-tasks/ # MoltBot skill: Tasks
│ └── mosaic-plugin-gantt/ # MoltBot skill: Gantt
├── docker/
│ ├── docker-compose.yml # Turnkey deployment
│ └── init-scripts/ # PostgreSQL init
├── docs/
│ ├── SETUP.md
│ ├── CONFIGURATION.md
│ └── DESIGN-PRINCIPLES.md
├── .env.example
├── turbo.json
├── pnpm-workspace.yaml
└── README.md
## Development Workflow
### Branch Strategy
- `main` — stable releases only
- `develop` — active development (default working branch)
- `feature/*` — feature branches from develop
- `fix/*` — bug fix branches
### Starting Work
```bash
git checkout develop
git pull --rebase
pnpm install
Running Locally
# Start all services (Docker)
docker compose up -d
# Or run individually for development
pnpm dev # All apps
pnpm dev:api # API only
pnpm dev:web # Web only
Testing
pnpm test # Run all tests
pnpm test:api # API tests only
pnpm test:web # Web tests only
pnpm test:e2e # Playwright E2E
Building
pnpm build # Build all
pnpm build:api # Build API
pnpm build:web # Build Web
Design Principles (NON-NEGOTIABLE)
PDA-Friendly Language
NEVER use demanding language. This is critical.
┌─────────────┬──────────────────────┐
│ ❌ NEVER │ ✅ ALWAYS │
├─────────────┼──────────────────────┤
│ OVERDUE │ Target passed │
├─────────────┼──────────────────────┤
│ URGENT │ Approaching target │
├─────────────┼──────────────────────┤
│ MUST DO │ Scheduled for │
├─────────────┼──────────────────────┤
│ CRITICAL │ High priority │
├─────────────┼──────────────────────┤
│ YOU NEED TO │ Consider / Option to │
├─────────────┼──────────────────────┤
│ REQUIRED │ Recommended │
└─────────────┴──────────────────────┘
Visual Indicators
Use status indicators consistently:
- 🟢 On track / Active
- 🔵 Upcoming / Scheduled
- ⏸️ Paused / On hold
- 💤 Dormant / Inactive
- ⚪ Not started
Display Principles
1. 10-second scannability — Key info visible immediately
2. Visual chunking — Clear sections with headers
3. Single-line items — Compact, scannable lists
4. Date grouping — Today, Tomorrow, This Week headers
5. Progressive disclosure — Details on click, not upfront
6. Calm colors — No aggressive reds for status
Reference
See docs/DESIGN-PRINCIPLES.md for complete guidelines.
For original patterns, see: jarvis-brain/docs/DESIGN-PRINCIPLES.md
API Conventions
Endpoints
GET /api/{resource} # List (with pagination, filters)
GET /api/{resource}/:id # Get single
POST /api/{resource} # Create
PATCH /api/{resource}/:id # Update
DELETE /api/{resource}/:id # Delete
Response Format
// Success
{
data: T | T[],
meta?: { total, page, limit }
}
// Error
{
error: {
code: string,
message: string,
details?: any
}
}
Brain Query API
POST /api/brain/query
{
query: "what's on my calendar",
context?: { view: "dashboard", workspace_id: "..." }
}
Database Conventions
Multi-Tenant (RLS)
All workspace-scoped tables use Row-Level Security:
- Always include workspace_id in queries
- RLS policies enforce isolation
- Set session context for current user
Prisma Commands
pnpm prisma:generate # Generate client
pnpm prisma:migrate # Run migrations
pnpm prisma:studio # Open Prisma Studio
pnpm prisma:seed # Seed development data
MoltBot Plugin Development
Plugins live in plugins/mosaic-plugin-*/ and follow MoltBot skill format:
# plugins/mosaic-plugin-brain/SKILL.md
---
name: mosaic-plugin-brain
description: Query Mosaic Stack for tasks, events, projects
version: 0.0.1
triggers:
- "what's on my calendar"
- "show my tasks"
- "morning briefing"
tools:
- mosaic_api
---
# Plugin instructions here...
Key principle: MoltBot remains stock. All customization via plugins only.
Environment Variables
See .env.example for all variables. Key ones:
# Database
DATABASE_URL=postgresql://mosaic:password@localhost:5432/mosaic
# Auth
AUTHENTIK_URL=https://auth.example.com
AUTHENTIK_CLIENT_ID=mosaic-stack
AUTHENTIK_CLIENT_SECRET=...
# Ollama
OLLAMA_MODE=local|remote
OLLAMA_ENDPOINT=http://localhost:11434
# MoltBot
MOSAIC_API_TOKEN=...
Issue Tracking
Issues are tracked at: https://git.mosaicstack.dev/mosaic/stack/issues
Labels
- Priority: p0 (critical), p1 (high), p2 (medium), p3 (low)
- Type: api, web, database, auth, plugin, ai, devops, docs, migration, security, testing,
performance, setup
Milestones
- M1-Foundation (0.0.x)
- M2-MultiTenant (0.0.x)
- M3-Features (0.0.x)
- M4-MoltBot (0.0.x)
- M5-Migration (0.1.0 MVP)
Commit Format
<type>(#issue): Brief description
Detailed explanation if needed.
Fixes #123
Types: feat, fix, docs, test, refactor, chore
Testing Requirements
- Minimum 85% coverage for new code
- Write tests before implementation (TDD)
- All tests must pass before PR merge
Docker Deployment
Turnkey (includes everything)
docker compose up -d
Customized (external services)
Create docker-compose.override.yml to:
- Point to external PostgreSQL/Valkey/Ollama
- Disable bundled services
See docs/DOCKER.md for details.
Key Documentation
┌───────────────────────────┬───────────────────────┐
│ Document │ Purpose │
├───────────────────────────┼───────────────────────┤
│ docs/SETUP.md │ Installation guide │
├───────────────────────────┼───────────────────────┤
│ docs/CONFIGURATION.md │ All config options │
├───────────────────────────┼───────────────────────┤
│ docs/DESIGN-PRINCIPLES.md │ PDA-friendly patterns │
├───────────────────────────┼───────────────────────┤
│ docs/DOCKER.md │ Docker deployment │
├───────────────────────────┼───────────────────────┤
│ docs/API.md │ API documentation │
└───────────────────────────┴───────────────────────┘
Related Repositories
┌──────────────┬──────────────────────────────────────────────┐
│ Repo │ Purpose │
├──────────────┼──────────────────────────────────────────────┤
│ jarvis-brain │ Original JSON-based brain (migration source) │
├──────────────┼──────────────────────────────────────────────┤
│ MoltBot │ Stock messaging gateway │
└──────────────┴──────────────────────────────────────────────┘
---
Mosaic Stack v0.0.x — Building the future of personal assistants.

18
apps/api/.swcrc Normal file
View File

@@ -0,0 +1,18 @@
{
"sourceMaps": true,
"jsc": {
"target": "es2022",
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"keepClassNames": true
},
"module": {
"type": "es6"
}
}

8
apps/api/nest-cli.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

40
apps/api/package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "@mosaic/api",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "nest build",
"dev": "nest start --watch",
"start": "node dist/main",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"src/**/*.ts\"",
"lint:fix": "eslint \"src/**/*.ts\" --fix",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:e2e": "vitest run --config ./vitest.e2e.config.ts"
},
"dependencies": {
"@nestjs/common": "^11.1.12",
"@nestjs/core": "^11.1.12",
"@nestjs/platform-express": "^11.1.12",
"@mosaic/shared": "workspace:*",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@mosaic/config": "workspace:*",
"@nestjs/cli": "^11.0.6",
"@nestjs/schematics": "^11.0.1",
"@nestjs/testing": "^11.1.12",
"@swc/core": "^1.10.18",
"@types/express": "^5.0.1",
"@types/node": "^22.13.4",
"typescript": "^5.8.2",
"unplugin-swc": "^1.5.2",
"vitest": "^3.0.8"
}
}

14
apps/api/src/app.controller.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
import { AppService } from "./app.service";
import type { ApiResponse } from "@mosaic/shared/types";
interface HealthStatus {
status: string;
timestamp: string;
}
export declare class AppController {
private readonly appService;
constructor(appService: AppService);
getHello(): string;
getHealth(): ApiResponse<HealthStatus>;
}
export {};
//# sourceMappingURL=app.controller.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.controller.d.ts","sourceRoot":"","sources":["app.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBACa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,UAAU;IAGnD,QAAQ,IAAI,MAAM;IAKlB,SAAS,IAAI,WAAW,CAAC,YAAY,CAAC;CASvC"}

View File

@@ -0,0 +1,50 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppController = void 0;
const common_1 = require("@nestjs/common");
const app_service_1 = require("./app.service");
let AppController = class AppController {
appService;
constructor(appService) {
this.appService = appService;
}
getHello() {
return this.appService.getHello();
}
getHealth() {
return {
success: true,
data: {
status: "healthy",
timestamp: new Date().toISOString(),
},
};
}
};
exports.AppController = AppController;
__decorate([
(0, common_1.Get)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", String)
], AppController.prototype, "getHello", null);
__decorate([
(0, common_1.Get)("health"),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Object)
], AppController.prototype, "getHealth", null);
exports.AppController = AppController = __decorate([
(0, common_1.Controller)(),
__metadata("design:paramtypes", [app_service_1.AppService])
], AppController);
//# sourceMappingURL=app.controller.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,+CAA2C;AASpC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACK;IAA7B,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAGvD,QAAQ;QACN,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACpC,CAAC;IAGD,SAAS;QACP,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,MAAM,EAAE,SAAS;gBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AAlBY,sCAAa;AAIxB;IADC,IAAA,YAAG,GAAE;;;;6CAGL;AAGD;IADC,IAAA,YAAG,EAAC,QAAQ,CAAC;;;;8CASb;wBAjBU,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAE8B,wBAAU;GADxC,aAAa,CAkBzB"}

View File

@@ -0,0 +1,31 @@
import { describe, expect, it } from "vitest";
import { AppService } from "./app.service";
import { AppController } from "./app.controller";
describe("AppController", () => {
const appService = new AppService();
const controller = new AppController(appService);
describe("getHello", () => {
it('should return "Mosaic Stack API"', () => {
expect(controller.getHello()).toBe("Mosaic Stack API");
});
});
describe("getHealth", () => {
it("should return health status", () => {
const result = controller.getHealth();
expect(result.success).toBe(true);
expect(result.data.status).toBe("healthy");
expect(result.data.timestamp).toBeDefined();
});
});
});
describe("AppService", () => {
const service = new AppService();
it('should return "Mosaic Stack API"', () => {
expect(service.getHello()).toBe("Mosaic Stack API");
});
});

View File

@@ -0,0 +1,29 @@
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
import type { ApiResponse } from "@mosaic/shared";
interface HealthStatus {
status: string;
timestamp: string;
}
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("health")
getHealth(): ApiResponse<HealthStatus> {
return {
success: true,
data: {
status: "healthy",
timestamp: new Date().toISOString(),
},
};
}
}

3
apps/api/src/app.module.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export declare class AppModule {
}
//# sourceMappingURL=app.module.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.module.d.ts","sourceRoot":"","sources":["app.module.ts"],"names":[],"mappings":"AAIA,qBAKa,SAAS;CAAG"}

View File

@@ -0,0 +1,23 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppModule = void 0;
const common_1 = require("@nestjs/common");
const app_controller_1 = require("./app.controller");
const app_service_1 = require("./app.service");
let AppModule = class AppModule {
};
exports.AppModule = AppModule;
exports.AppModule = AppModule = __decorate([
(0, common_1.Module)({
imports: [],
controllers: [app_controller_1.AppController],
providers: [app_service_1.AppService],
})
], AppModule);
//# sourceMappingURL=app.module.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qDAAiD;AACjD,+CAA2C;AAOpC,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IALrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,CAAC,8BAAa,CAAC;QAC5B,SAAS,EAAE,CAAC,wBAAU,CAAC;KACxB,CAAC;GACW,SAAS,CAAG"}

View File

@@ -0,0 +1,10 @@
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

4
apps/api/src/app.service.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
export declare class AppService {
getHello(): string;
}
//# sourceMappingURL=app.service.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.service.d.ts","sourceRoot":"","sources":["app.service.ts"],"names":[],"mappings":"AAEA,qBACa,UAAU;IACrB,QAAQ,IAAI,MAAM;CAGnB"}

View File

@@ -0,0 +1,20 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppService = void 0;
const common_1 = require("@nestjs/common");
let AppService = class AppService {
getHello() {
return "Mosaic Stack API";
}
};
exports.AppService = AppService;
exports.AppService = AppService = __decorate([
(0, common_1.Injectable)()
], AppService);
//# sourceMappingURL=app.service.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"app.service.js","sourceRoot":"","sources":["app.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAGrC,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,QAAQ;QACN,OAAO,kBAAkB,CAAC;IAC5B,CAAC;CACF,CAAA;AAJY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAItB"}

View File

@@ -0,0 +1,8 @@
import { Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
getHello(): string {
return "Mosaic Stack API";
}
}

2
apps/api/src/main.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=main.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":""}

16
apps/api/src/main.js Normal file
View File

@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@nestjs/core");
const app_module_1 = require("./app.module");
async function bootstrap() {
const app = await core_1.NestFactory.create(app_module_1.AppModule);
app.enableCors();
const port = process.env["PORT"] ?? 3001;
await app.listen(port);
console.log(`API running on http://localhost:${port}`);
}
bootstrap().catch((err) => {
console.error("Failed to start application:", err);
process.exit(1);
});
//# sourceMappingURL=main.js.map

1
apps/api/src/main.js.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAChD,GAAG,CAAC,UAAU,EAAE,CAAC;IAEjB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IACzC,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEvB,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACjC,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}

17
apps/api/src/main.ts Normal file
View File

@@ -0,0 +1,17 @@
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
const port = process.env["PORT"] ?? 3001;
await app.listen(port);
console.log(`API running on http://localhost:${port}`);
}
bootstrap().catch((err: unknown) => {
console.error("Failed to start application:", err);
process.exit(1);
});

13
apps/api/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"extends": "@mosaic/config/typescript/nestjs",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
}

27
apps/api/vitest.config.ts Normal file
View File

@@ -0,0 +1,27 @@
import swc from "unplugin-swc";
import { defineConfig } from "vitest/config";
import path from "path";
export default defineConfig({
test: {
globals: false,
environment: "node",
include: ["src/**/*.test.ts", "src/**/*.spec.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["node_modules/", "dist/"],
},
setupFiles: ["./vitest.setup.ts"],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
plugins: [
swc.vite({
module: { type: "es6" },
}),
],
});

1
apps/api/vitest.setup.ts Normal file
View File

@@ -0,0 +1 @@
import "reflect-metadata";

6
apps/web/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

7
apps/web/next.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
transpilePackages: ["@mosaic/ui", "@mosaic/shared"],
};
export default nextConfig;

36
apps/web/package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "@mosaic/web",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev --turbopack --port 3000",
"start": "next start",
"lint": "next lint",
"lint:fix": "next lint --fix",
"typecheck": "tsc --noEmit",
"clean": "rm -rf .next",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@mosaic/shared": "workspace:*",
"@mosaic/ui": "workspace:*",
"next": "^16.1.6",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@mosaic/config": "workspace:*",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/node": "^22.13.4",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"jsdom": "^26.0.0",
"typescript": "^5.8.2",
"vitest": "^3.0.8"
}
}

View File

@@ -0,0 +1,20 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: rgb(var(--background-rgb));
}

View File

@@ -0,0 +1,16 @@
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";
export const metadata: Metadata = {
title: "Mosaic Stack",
description: "Mosaic Stack Web Application",
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

View File

@@ -0,0 +1,22 @@
import { describe, expect, it, afterEach } from "vitest";
import { render, screen, cleanup } from "@testing-library/react";
import Home from "./page";
afterEach(() => {
cleanup();
});
describe("Home", () => {
it("should render the title", () => {
render(<Home />);
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Mosaic Stack");
});
it("should render the buttons", () => {
render(<Home />);
const buttons = screen.getAllByRole("button");
expect(buttons.length).toBe(2);
expect(buttons[0]).toHaveTextContent("Get Started");
expect(buttons[1]).toHaveTextContent("Learn More");
});
});

16
apps/web/src/app/page.tsx Normal file
View File

@@ -0,0 +1,16 @@
import { Button } from "@mosaic/ui";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<h1 className="text-4xl font-bold mb-8">Mosaic Stack</h1>
<p className="text-lg text-gray-600 mb-8">
Welcome to the Mosaic Stack monorepo
</p>
<div className="flex gap-4">
<Button variant="primary">Get Started</Button>
<Button variant="secondary">Learn More</Button>
</div>
</main>
);
}

11
apps/web/tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"extends": "@mosaic/config/typescript/nextjs",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

23
apps/web/vitest.config.ts Normal file
View File

@@ -0,0 +1,23 @@
import { defineConfig } from "vitest/config";
import path from "path";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
globals: false,
environment: "jsdom",
include: ["src/**/*.test.tsx", "src/**/*.test.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["node_modules/", ".next/"],
},
setupFiles: ["./vitest.setup.ts"],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});

1
apps/web/vitest.setup.ts Normal file
View File

@@ -0,0 +1 @@
import "@testing-library/jest-dom/vitest";

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/.prettierrc.js
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:21:58
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-.prettierrc.js_20260128-1321_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.controller.test.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:44
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.controller.test.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.controller.test.ts
**Tool Used:** Edit
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:27:46
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.controller.test.ts_20260128-1327_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.controller.test.ts
**Tool Used:** Edit
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:28:44
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.controller.test.ts_20260128-1328_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.controller.test.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:29:44
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.controller.test.ts_20260128-1329_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.controller.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:34
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.controller.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.controller.ts
**Tool Used:** Edit
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:26:43
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.controller.ts_20260128-1326_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.module.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:33
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.module.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/app.service.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:35
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-app.service.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/src/main.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:32
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-src-main.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/vitest.config.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:46
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-vitest.config.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/vitest.config.ts
**Tool Used:** Edit
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:28:15
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-vitest.config.ts_20260128-1328_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/api/vitest.setup.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:28:13
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-api-vitest.setup.ts_20260128-1328_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/next-env.d.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:21:10
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-next-env.d.ts_20260128-1321_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/next.config.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:21:08
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-next.config.ts_20260128-1321_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/src/app/layout.tsx
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:21:23
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-src-app-layout.tsx_20260128-1321_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/src/app/page.test.tsx
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:27:21
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-src-app-page.test.tsx_20260128-1327_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/src/app/page.test.tsx
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:30:10
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-src-app-page.test.tsx_20260128-1330_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/src/app/page.tsx
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:21:24
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-src-app-page.tsx_20260128-1321_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/vitest.config.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:21:26
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-vitest.config.ts_20260128-1321_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/apps/web/vitest.setup.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:21:28
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-apps-web-vitest.setup.ts_20260128-1321_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/eslint.config.js
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:22:06
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-eslint.config.js_20260128-1322_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/config/eslint/base.js
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:18:48
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-config-eslint-base.js_20260128-1318_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/config/eslint/nestjs.js
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:18:49
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-config-eslint-nestjs.js_20260128-1318_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/config/eslint/nextjs.js
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:18:48
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-config-eslint-nextjs.js_20260128-1318_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/config/prettier/index.js
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:18:50
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-config-prettier-index.js_20260128-1318_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/shared/src/index.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:26:18
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-shared-src-index.ts_20260128-1326_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/shared/src/types/index.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:19:23
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-shared-src-types-index.ts_20260128-1319_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/shared/src/utils/index.test.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:19:25
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-shared-src-utils-index.test.ts_20260128-1319_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/shared/src/utils/index.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:19:24
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-shared-src-utils-index.ts_20260128-1319_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/shared/vitest.config.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:19:25
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-shared-vitest.config.ts_20260128-1319_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/ui/src/components/Button.test.tsx
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:19:59
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-ui-src-components-Button.test.tsx_20260128-1319_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/ui/src/components/Button.test.tsx
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:30:08
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-ui-src-components-Button.test.tsx_20260128-1330_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/ui/src/components/Button.tsx
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:19:49
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-ui-src-components-Button.tsx_20260128-1319_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/ui/src/index.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:19:48
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-ui-src-index.ts_20260128-1319_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/ui/vitest.config.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:00
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-ui-vitest.config.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,17 @@
# QA Remediation Report
**File:** /home/localadmin/src/mosaic-stack/packages/ui/vitest.setup.ts
**Tool Used:** Write
**Epic:** general
**Iteration:** 1
**Generated:** 2026-01-28 13:20:01
## Status
Pending QA validation
## Next Steps
This report was created by the QA automation hook.
To process this report, run:
```bash
claude -p "Use Task tool to launch universal-qa-agent for report: /home/localadmin/src/mosaic-stack/docs/reports/qa-automation/pending/home-localadmin-src-mosaic-stack-packages-ui-vitest.setup.ts_20260128-1320_1_remediation_needed.md"
```

View File

@@ -0,0 +1,97 @@
# Issue #1: Project scaffold (monorepo, NestJS, Next.js 16)
## Objective
Set up the monorepo structure with pnpm workspaces + TurboRepo containing:
- apps/api (NestJS)
- apps/web (Next.js 16)
- packages/shared (types, utilities)
- packages/ui (shared components)
- packages/config (shared configuration)
## Requirements
- pnpm workspace configuration
- TurboRepo for build orchestration
- TypeScript strict mode
- ESLint + Prettier
- Vitest for unit tests
- Initial package.json scripts
## Approach
1. Initialize root package.json with pnpm workspaces
2. Configure TurboRepo (turbo.json)
3. Set up shared packages first (config, shared, ui)
4. Create NestJS API app
5. Create Next.js 16 web app
6. Configure TypeScript, ESLint, Prettier at root level
7. Set up Vitest for testing
8. Add build/dev/test scripts
## Progress
- [x] Initialize pnpm workspace configuration
- [x] Set up TurboRepo for build orchestration
- [x] Create packages/config
- [x] Create packages/shared
- [x] Create packages/ui
- [x] Create apps/api (NestJS)
- [x] Create apps/web (Next.js 16)
- [x] Configure TypeScript strict mode
- [x] Set up ESLint + Prettier
- [x] Configure Vitest
- [x] Add initial package.json scripts
- [x] Test build and verify
## Testing Results
- `pnpm build` - All 4 packages build successfully
- `pnpm test` - All 19 tests pass (shared: 10, api: 3, ui: 4, web: 2)
## Structure Created
```
mosaic-stack/
├── apps/
│ ├── api/ # NestJS 11.1.12 API
│ │ ├── src/
│ │ │ ├── main.ts
│ │ │ ├── app.module.ts
│ │ │ ├── app.controller.ts
│ │ │ └── app.service.ts
│ │ └── package.json
│ └── web/ # Next.js 16.1.6 with App Router
│ ├── src/app/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── globals.css
│ └── package.json
├── packages/
│ ├── config/ # Shared TypeScript, ESLint, Prettier configs
│ │ ├── typescript/
│ │ ├── eslint/
│ │ └── prettier/
│ ├── shared/ # Shared types and utilities
│ │ └── src/
│ │ ├── types/
│ │ └── utils/
│ └── ui/ # Shared React components
│ └── src/
│ └── components/
├── package.json # Root package.json with scripts
├── pnpm-workspace.yaml # Workspace configuration
├── turbo.json # TurboRepo configuration
├── tsconfig.json # Root TypeScript config
├── eslint.config.js # Root ESLint config
└── .prettierrc.js # Root Prettier config
```
## Key Scripts
- `pnpm dev` - Start all dev servers (API: 3001, Web: 3000)
- `pnpm build` - Build all packages
- `pnpm test` - Run all tests
- `pnpm lint` - Run linting
- `pnpm format` - Format all files
## Notes
- Version: 0.0.1 (M1-Foundation milestone)
- Using pnpm 10.19.0 for package management
- TurboRepo 2.8.0 for efficient build caching
- Next.js 16.1.6 with Turbopack for dev
- NestJS 11.1.12 with Vitest for testing
- TypeScript 5.8.2 in strict mode

13
eslint.config.js Normal file
View File

@@ -0,0 +1,13 @@
import baseConfig from "@mosaic/config/eslint/base";
export default [
...baseConfig,
{
languageOptions: {
parserOptions: {
project: ["./tsconfig.json", "./apps/*/tsconfig.json", "./packages/*/tsconfig.json"],
tsconfigRootDir: import.meta.dirname,
},
},
},
];

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "mosaic-stack",
"version": "0.0.1",
"private": true,
"type": "module",
"packageManager": "pnpm@10.19.0",
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
"test": "turbo run test",
"test:watch": "turbo run test:watch",
"test:coverage": "turbo run test:coverage",
"clean": "turbo run clean && rm -rf node_modules",
"typecheck": "turbo run typecheck"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.26.0",
"@typescript-eslint/parser": "^8.26.0",
"eslint": "^9.21.0",
"eslint-config-prettier": "^10.1.0",
"eslint-plugin-prettier": "^5.2.3",
"prettier": "^3.5.3",
"turbo": "^2.8.0",
"typescript": "^5.8.2",
"vitest": "^3.0.8"
}
}

View File

@@ -0,0 +1,32 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import prettierConfig from "eslint-config-prettier";
import prettierPlugin from "eslint-plugin-prettier";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
prettierConfig,
{
plugins: {
prettier: prettierPlugin,
},
rules: {
"prettier/prettier": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/consistent-type-imports": [
"error",
{ prefer: "type-imports" },
],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
},
},
{
ignores: ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/coverage/**"],
}
);

View File

@@ -0,0 +1,12 @@
import baseConfig from "./base.js";
export default [
...baseConfig,
{
rules: {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
},
},
];

View File

@@ -0,0 +1,17 @@
import baseConfig from "./base.js";
export default [
...baseConfig,
{
rules: {
"@typescript-eslint/no-misused-promises": [
"error",
{
checksVoidReturn: {
attributes: false,
},
},
],
},
},
];

View File

@@ -0,0 +1,29 @@
{
"name": "@mosaic/config",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
"./typescript/base": "./typescript/base.json",
"./typescript/nextjs": "./typescript/nextjs.json",
"./typescript/nestjs": "./typescript/nestjs.json",
"./typescript/library": "./typescript/library.json",
"./eslint/base": "./eslint/base.js",
"./eslint/nextjs": "./eslint/nextjs.js",
"./eslint/nestjs": "./eslint/nestjs.js",
"./prettier": "./prettier/index.js"
},
"dependencies": {
"@eslint/js": "^9.21.0",
"@typescript-eslint/eslint-plugin": "^8.26.0",
"@typescript-eslint/parser": "^8.26.0",
"eslint": "^9.21.0",
"eslint-config-prettier": "^10.1.0",
"eslint-plugin-prettier": "^5.2.3",
"prettier": "^3.5.3",
"typescript-eslint": "^8.26.0"
},
"devDependencies": {
"typescript": "^5.8.2"
}
}

View File

@@ -0,0 +1,13 @@
/** @type {import("prettier").Config} */
const config = {
semi: true,
singleQuote: false,
tabWidth: 2,
trailingComma: "es5",
printWidth: 100,
bracketSpacing: true,
arrowParens: "always",
endOfLine: "lf",
};
export default config;

View File

@@ -0,0 +1,30 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"]
}
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"composite": true
}
}

View File

@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"incremental": true,
"target": "ES2022"
}
}

View File

@@ -0,0 +1,18 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"jsx": "preserve",
"module": "ESNext",
"moduleResolution": "bundler",
"allowJs": true,
"noEmit": true,
"incremental": true,
"plugins": [
{
"name": "next"
}
]
}
}

View File

@@ -0,0 +1,40 @@
{
"name": "@mosaic/shared",
"version": "0.0.1",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.js"
},
"./types": {
"types": "./dist/types/index.d.ts",
"require": "./dist/types/index.js",
"import": "./dist/types/index.js"
},
"./utils": {
"types": "./dist/utils/index.d.ts",
"require": "./dist/utils/index.js",
"import": "./dist/utils/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
},
"devDependencies": {
"@mosaic/config": "workspace:*",
"typescript": "^5.8.2",
"vitest": "^3.0.8"
}
}

View File

@@ -0,0 +1,2 @@
export * from "./types/index";
export * from "./utils/index";

View File

@@ -0,0 +1,38 @@
/**
* Base entity type with common fields
*/
export interface BaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
}
/**
* API response wrapper
*/
export interface ApiResponse<T> {
data: T;
success: boolean;
message?: string;
}
/**
* Pagination parameters
*/
export interface PaginationParams {
page: number;
limit: number;
sortBy?: string;
sortOrder?: "asc" | "desc";
}
/**
* Paginated response
*/
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}

View File

@@ -0,0 +1,61 @@
import { describe, expect, it } from "vitest";
import { isDefined, parseDate, sleep, slugify } from "./index.js";
describe("parseDate", () => {
it("should return undefined for null or undefined", () => {
expect(parseDate(null)).toBeUndefined();
expect(parseDate(undefined)).toBeUndefined();
});
it("should return the same Date object if passed a Date", () => {
const date = new Date("2024-01-15");
expect(parseDate(date)).toBe(date);
});
it("should parse valid date strings", () => {
const result = parseDate("2024-01-15");
expect(result).toBeInstanceOf(Date);
expect(result?.toISOString()).toContain("2024-01-15");
});
it("should return undefined for invalid date strings", () => {
expect(parseDate("not-a-date")).toBeUndefined();
});
});
describe("slugify", () => {
it("should convert text to lowercase slug", () => {
expect(slugify("Hello World")).toBe("hello-world");
});
it("should handle special characters", () => {
expect(slugify("Hello, World!")).toBe("hello-world");
});
it("should trim leading and trailing hyphens", () => {
expect(slugify(" Hello World ")).toBe("hello-world");
});
});
describe("sleep", () => {
it("should resolve after the specified time", async () => {
const start = Date.now();
await sleep(50);
const elapsed = Date.now() - start;
expect(elapsed).toBeGreaterThanOrEqual(45);
});
});
describe("isDefined", () => {
it("should return false for null and undefined", () => {
expect(isDefined(null)).toBe(false);
expect(isDefined(undefined)).toBe(false);
});
it("should return true for defined values", () => {
expect(isDefined(0)).toBe(true);
expect(isDefined("")).toBe(true);
expect(isDefined(false)).toBe(true);
expect(isDefined({})).toBe(true);
});
});

View File

@@ -0,0 +1,35 @@
/**
* Safely parse a date string or return undefined
*/
export function parseDate(value: string | Date | undefined | null): Date | undefined {
if (!value) return undefined;
if (value instanceof Date) return value;
const parsed = new Date(value);
return isNaN(parsed.getTime()) ? undefined : parsed;
}
/**
* Generate a simple slug from a string
*/
export function slugify(text: string): string {
return text
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "")
.replace(/[\s_-]+/g, "-")
.replace(/^-+|-+$/g, "");
}
/**
* Sleep for a given number of milliseconds
*/
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Check if a value is defined (not null or undefined)
*/
export function isDefined<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}

View File

@@ -0,0 +1,12 @@
{
"extends": "@mosaic/config/typescript/library",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"module": "CommonJS",
"moduleResolution": "node",
"lib": ["ES2022", "DOM"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}

View File

@@ -0,0 +1,14 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: false,
environment: "node",
include: ["src/**/*.test.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["node_modules/", "dist/"],
},
},
});

42
packages/ui/package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "@mosaic/ui",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./components/*": {
"types": "./dist/components/*.d.ts",
"import": "./dist/components/*.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"react": "^19.0.0"
},
"devDependencies": {
"@mosaic/config": "workspace:*",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@types/react": "^19.0.8",
"jsdom": "^26.0.0",
"typescript": "^5.8.2",
"vitest": "^3.0.8"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}

View File

@@ -0,0 +1,31 @@
import { describe, expect, it, afterEach } from "vitest";
import { render, screen, cleanup } from "@testing-library/react";
import { Button } from "./Button.js";
afterEach(() => {
cleanup();
});
describe("Button", () => {
it("should render children", () => {
render(<Button>Click me</Button>);
expect(screen.getByRole("button")).toHaveTextContent("Click me");
});
it("should apply variant styles", () => {
render(<Button variant="danger">Delete</Button>);
const button = screen.getByRole("button");
expect(button.className).toContain("bg-red-600");
});
it("should apply size styles", () => {
render(<Button size="lg">Large Button</Button>);
const button = screen.getByRole("button");
expect(button.className).toContain("px-6");
});
it("should pass through additional props", () => {
render(<Button disabled>Disabled</Button>);
expect(screen.getByRole("button")).toBeDisabled();
});
});

Some files were not shown because too many files have changed in this diff Show More