diff --git a/docs/superpowers/plans/2026-06-14-vynte-scheduler-replacement.md b/docs/superpowers/plans/2026-06-14-vynte-scheduler-replacement.md
new file mode 100644
index 0000000000..a34ecc9f23
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-14-vynte-scheduler-replacement.md
@@ -0,0 +1,966 @@
+# Vynte Scheduler Replacement Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Build a small internal scheduler app at `apps/scheduler` that replaces Cal.diy's current interface for Authentik-only Vynte team scheduling.
+
+**Architecture:** Add a separate Next.js App Router app that shares Cal's database, auth session, and reusable scheduling primitives, but owns a small scheduler-specific UI and API surface. Keep the first version table-free: use existing `User`, `Schedule`, `Availability`, `Credential`, `SelectedCalendar`, and `Booking` data. Implement the product as a local runnable MVP with narrow service boundaries for future deeper Cal booking integration.
+
+**Tech Stack:** Next.js App Router, React, TypeScript, CSS modules/global CSS, Prisma via `@calcom/prisma`, existing Cal auth helper, Vitest, Yarn 4 workspaces.
+
+---
+
+## File Structure
+
+- Create `apps/scheduler/package.json`: workspace package scripts and dependencies.
+- Create `apps/scheduler/next.config.ts`: minimal Next config for a monorepo app.
+- Create `apps/scheduler/tsconfig.json`: extends `@calcom/tsconfig/nextjs.json`.
+- Create `apps/scheduler/next-env.d.ts`: Next type marker.
+- Create `apps/scheduler/app/layout.tsx`: root layout.
+- Create `apps/scheduler/app/page.tsx`: redirect to `/calendar`.
+- Create `apps/scheduler/app/globals.css`: approved visual system.
+- Create `apps/scheduler/app/calendar/page.tsx`: calendar-first scheduling screen.
+- Create `apps/scheduler/app/availability/page.tsx`: weekly schedule plus overrides.
+- Create `apps/scheduler/app/connections/page.tsx`: calendar and conferencing provider screen.
+- Create `apps/scheduler/app/api/scheduler/team/route.ts`: team members API.
+- Create `apps/scheduler/app/api/scheduler/availability/route.ts`: mutual availability API.
+- Create `apps/scheduler/app/api/scheduler/schedule/route.ts`: schedule read/write API.
+- Create `apps/scheduler/app/api/scheduler/connections/route.ts`: provider metadata API.
+- Create `apps/scheduler/app/api/scheduler/meetings/route.ts`: meetings list/create API.
+- Create `apps/scheduler/components/AppShell.tsx`: side navigation and page frame.
+- Create `apps/scheduler/components/CalendarWorkspace.tsx`: interactive calendar and composer.
+- Create `apps/scheduler/components/AvailabilityEditor.tsx`: weekly schedule editor.
+- Create `apps/scheduler/components/ConnectionsView.tsx`: provider list.
+- Create `apps/scheduler/lib/scheduler/types.ts`: shared domain types.
+- Create `apps/scheduler/lib/scheduler/time.ts`: week and time helpers.
+- Create `apps/scheduler/lib/scheduler/privacy.ts`: free/busy privacy filtering.
+- Create `apps/scheduler/lib/scheduler/availability.ts`: mutual availability calculation.
+- Create `apps/scheduler/lib/scheduler/service.ts`: server service facade.
+- Create `apps/scheduler/lib/scheduler/auth.ts`: Authentik session guard.
+- Create `apps/scheduler/lib/scheduler/legacy-request.ts`: converts App Router request data for Cal auth helper.
+- Create `apps/scheduler/lib/scheduler/demo-data.ts`: deterministic fallback data used only when `SCHEDULER_DEMO_MODE=1`.
+- Create `apps/scheduler/lib/scheduler/*.test.ts`: unit tests for privacy, time, and availability helpers.
+
+## Task 1: Scaffold the Scheduler App Shell
+
+**Files:**
+- Create: `apps/scheduler/package.json`
+- Create: `apps/scheduler/next.config.ts`
+- Create: `apps/scheduler/tsconfig.json`
+- Create: `apps/scheduler/next-env.d.ts`
+- Create: `apps/scheduler/app/layout.tsx`
+- Create: `apps/scheduler/app/page.tsx`
+- Create: `apps/scheduler/app/globals.css`
+- Create: `apps/scheduler/components/AppShell.tsx`
+
+- [ ] **Step 1: Create the workspace package**
+
+Create `apps/scheduler/package.json`:
+
+```json
+{
+ "name": "@vynte/scheduler",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --port 3040",
+ "build": "next build",
+ "start": "next start --port 3040",
+ "type-check": "tsc --noEmit",
+ "test": "vitest run"
+ },
+ "dependencies": {
+ "@calcom/dayjs": "workspace:*",
+ "@calcom/features": "workspace:*",
+ "@calcom/lib": "workspace:*",
+ "@calcom/prisma": "workspace:*",
+ "@calcom/tsconfig": "workspace:*",
+ "@calcom/types": "workspace:*",
+ "next": "16.1.5",
+ "next-auth": "4.24.11",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
+ "zod": "^4.1.13"
+ },
+ "devDependencies": {
+ "@types/node": "^20.17.23",
+ "@types/react": "18.0.26",
+ "@types/react-dom": "^18.0.9",
+ "typescript": "5.9.3",
+ "vitest": "4.1.8"
+ }
+}
+```
+
+- [ ] **Step 2: Add Next and TypeScript config**
+
+Create `apps/scheduler/next.config.ts`:
+
+```ts
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ transpilePackages: ["@calcom/dayjs", "@calcom/features", "@calcom/lib", "@calcom/prisma", "@calcom/types"],
+ experimental: {
+ externalDir: true,
+ },
+};
+
+export default nextConfig;
+```
+
+Create `apps/scheduler/tsconfig.json`:
+
+```json
+{
+ "extends": "@calcom/tsconfig/nextjs.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@scheduler/*": ["./*"]
+ },
+ "plugins": [{ "name": "next" }],
+ "strictNullChecks": true
+ },
+ "include": [
+ "next-env.d.ts",
+ "../../packages/types/*.d.ts",
+ "../../packages/types/next-auth.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": ["node_modules", ".next"]
+}
+```
+
+Create `apps/scheduler/next-env.d.ts`:
+
+```ts
+///
+///
+```
+
+- [ ] **Step 3: Add layout, redirect, and global CSS**
+
+Create `apps/scheduler/app/layout.tsx`:
+
+```tsx
+import type { Metadata } from "next";
+import "./globals.css";
+
+export const metadata: Metadata = {
+ title: "Vynte Schedule",
+ description: "Internal Vynte team scheduling",
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
{children}
+
+ );
+}
+```
+
+Create `apps/scheduler/app/page.tsx`:
+
+```tsx
+import { redirect } from "next/navigation";
+
+export default function Page() {
+ redirect("/calendar");
+}
+```
+
+Create `apps/scheduler/app/globals.css` with the approved visual system:
+
+```css
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ min-height: 100%;
+ background: #f4f5f7;
+ color: #202226;
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+}
+
+button,
+input,
+select,
+textarea {
+ font: inherit;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.app-frame {
+ min-height: 100vh;
+ display: grid;
+ grid-template-columns: 196px minmax(0, 1fr);
+ background: #f4f5f7;
+}
+
+.sidebar {
+ min-height: 100vh;
+ border-right: 1px solid #e3e5e8;
+ background: #fafafa;
+ padding: 18px 12px;
+ display: flex;
+ flex-direction: column;
+}
+
+.brand {
+ font-size: 16px;
+ font-weight: 750;
+ padding: 3px 8px 20px;
+}
+
+.nav-list {
+ display: grid;
+ gap: 4px;
+}
+
+.nav-item {
+ padding: 9px 10px;
+ border-radius: 5px;
+ color: #555a61;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.nav-item[data-active="true"] {
+ background: #e9efec;
+ color: #204d39;
+}
+
+.profile-block {
+ margin-top: auto;
+ border-top: 1px solid #e3e5e8;
+ padding: 14px 8px 2px;
+ font-size: 12px;
+}
+
+.profile-block strong {
+ display: block;
+ font-size: 13px;
+ margin-bottom: 2px;
+}
+
+.profile-block span {
+ color: #777c83;
+}
+
+.main-surface {
+ padding: 20px;
+ min-width: 0;
+}
+
+@media (max-width: 860px) {
+ .app-frame {
+ grid-template-columns: 1fr;
+ }
+
+ .sidebar {
+ min-height: auto;
+ border-right: 0;
+ border-bottom: 1px solid #e3e5e8;
+ }
+}
+```
+
+- [ ] **Step 4: Add shared shell**
+
+Create `apps/scheduler/components/AppShell.tsx`:
+
+```tsx
+import Link from "next/link";
+
+type NavItem = {
+ href: string;
+ label: string;
+};
+
+const navItems: NavItem[] = [
+ { href: "/calendar", label: "Calendar" },
+ { href: "/availability", label: "Availability" },
+ { href: "/connections", label: "Connections" },
+];
+
+export function AppShell({
+ active,
+ user,
+ children,
+}: {
+ active: "calendar" | "availability" | "connections";
+ user: { name: string; timeZone: string };
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
+```
+
+- [ ] **Step 5: Verify scaffold**
+
+Run:
+
+```bash
+node .yarn/releases/yarn-4.12.0.cjs workspace @vynte/scheduler type-check
+```
+
+Expected: TypeScript completes without errors.
+
+- [ ] **Step 6: Commit scaffold**
+
+```bash
+git add apps/scheduler
+git commit -m "Add Vynte scheduler app shell"
+```
+
+## Task 2: Implement Scheduler Domain Helpers
+
+**Files:**
+- Create: `apps/scheduler/lib/scheduler/types.ts`
+- Create: `apps/scheduler/lib/scheduler/time.ts`
+- Create: `apps/scheduler/lib/scheduler/privacy.ts`
+- Create: `apps/scheduler/lib/scheduler/availability.ts`
+- Create: `apps/scheduler/lib/scheduler/privacy.test.ts`
+- Create: `apps/scheduler/lib/scheduler/availability.test.ts`
+
+- [ ] **Step 1: Add shared domain types**
+
+Create `apps/scheduler/lib/scheduler/types.ts`:
+
+```ts
+export type SchedulerUser = {
+ id: number;
+ name: string;
+ email: string;
+ timeZone: string;
+};
+
+export type BusyBlock = {
+ id: string;
+ userId: number;
+ start: string;
+ end: string;
+ title?: string;
+ source: "internal" | "external" | "availability";
+};
+
+export type PublicBusyBlock = Pick & {
+ title?: string;
+};
+
+export type WeeklyRange = {
+ day: number;
+ enabled: boolean;
+ startTime: string;
+ endTime: string;
+};
+
+export type DateOverride = {
+ date: string;
+ unavailable: boolean;
+ startTime?: string;
+ endTime?: string;
+};
+
+export type SchedulerSchedule = {
+ id: number | null;
+ userId: number;
+ timeZone: string;
+ weekly: WeeklyRange[];
+ overrides: DateOverride[];
+};
+
+export type MutualSlot = {
+ start: string;
+ end: string;
+ attendeeIds: number[];
+};
+
+export type MeetingDraft = {
+ title: string;
+ attendeeIds: number[];
+ start: string;
+ durationMinutes: number;
+ location?: string;
+ conferencing?: "none" | "provider";
+};
+```
+
+- [ ] **Step 2: Write failing privacy tests**
+
+Create `apps/scheduler/lib/scheduler/privacy.test.ts`:
+
+```ts
+import { describe, expect, it } from "vitest";
+import { filterBusyBlocksForViewer } from "./privacy";
+import type { BusyBlock } from "./types";
+
+const blocks: BusyBlock[] = [
+ {
+ id: "own",
+ userId: 1,
+ start: "2026-06-15T16:00:00.000Z",
+ end: "2026-06-15T16:30:00.000Z",
+ title: "Product review",
+ source: "internal",
+ },
+ {
+ id: "other",
+ userId: 2,
+ start: "2026-06-15T17:00:00.000Z",
+ end: "2026-06-15T17:30:00.000Z",
+ title: "Private vendor call",
+ source: "external",
+ },
+];
+
+describe("filterBusyBlocksForViewer", () => {
+ it("keeps titles for the viewer's own blocks", () => {
+ expect(filterBusyBlocksForViewer(blocks, 1)[0]).toMatchObject({ title: "Product review" });
+ });
+
+ it("removes titles for teammate blocks", () => {
+ expect(filterBusyBlocksForViewer(blocks, 1)[1]).not.toHaveProperty("title");
+ });
+});
+```
+
+- [ ] **Step 3: Implement privacy helper**
+
+Create `apps/scheduler/lib/scheduler/privacy.ts`:
+
+```ts
+import type { BusyBlock, PublicBusyBlock } from "./types";
+
+export function filterBusyBlocksForViewer(blocks: BusyBlock[], viewerId: number): PublicBusyBlock[] {
+ return blocks.map((block) => {
+ const base = {
+ id: block.id,
+ userId: block.userId,
+ start: block.start,
+ end: block.end,
+ source: block.source,
+ };
+
+ if (block.userId === viewerId && block.title) {
+ return { ...base, title: block.title };
+ }
+
+ return base;
+ });
+}
+```
+
+- [ ] **Step 4: Write failing mutual availability tests**
+
+Create `apps/scheduler/lib/scheduler/availability.test.ts`:
+
+```ts
+import { describe, expect, it } from "vitest";
+import { computeMutualSlots } from "./availability";
+import type { BusyBlock, SchedulerSchedule } from "./types";
+
+const schedules: SchedulerSchedule[] = [
+ {
+ id: 1,
+ userId: 1,
+ timeZone: "America/Denver",
+ weekly: [{ day: 1, enabled: true, startTime: "09:00", endTime: "11:00" }],
+ overrides: [],
+ },
+ {
+ id: 2,
+ userId: 2,
+ timeZone: "America/Denver",
+ weekly: [{ day: 1, enabled: true, startTime: "09:30", endTime: "11:30" }],
+ overrides: [],
+ },
+];
+
+describe("computeMutualSlots", () => {
+ it("returns mutual slots that avoid busy blocks", () => {
+ const busy: BusyBlock[] = [
+ {
+ id: "busy",
+ userId: 2,
+ start: "2026-06-15T16:00:00.000Z",
+ end: "2026-06-15T16:30:00.000Z",
+ source: "external",
+ },
+ ];
+
+ const slots = computeMutualSlots({
+ schedules,
+ busy,
+ rangeStart: "2026-06-15T00:00:00.000Z",
+ rangeEnd: "2026-06-16T00:00:00.000Z",
+ durationMinutes: 30,
+ });
+
+ expect(slots.map((slot) => slot.start)).toEqual([
+ "2026-06-15T15:30:00.000Z",
+ "2026-06-15T16:30:00.000Z",
+ ]);
+ });
+});
+```
+
+- [ ] **Step 5: Implement time and availability helpers**
+
+Create `apps/scheduler/lib/scheduler/time.ts`:
+
+```ts
+export const MINUTE = 60_000;
+
+export function addMinutes(date: Date, minutes: number) {
+ return new Date(date.getTime() + minutes * MINUTE);
+}
+
+export function overlaps(a: { start: Date; end: Date }, b: { start: Date; end: Date }) {
+ return a.start < b.end && b.start < a.end;
+}
+
+export function toIso(date: Date) {
+ return date.toISOString();
+}
+```
+
+Create `apps/scheduler/lib/scheduler/availability.ts`:
+
+```ts
+import { addMinutes, overlaps, toIso } from "./time";
+import type { BusyBlock, MutualSlot, SchedulerSchedule } from "./types";
+
+type ComputeInput = {
+ schedules: SchedulerSchedule[];
+ busy: BusyBlock[];
+ rangeStart: string;
+ rangeEnd: string;
+ durationMinutes: number;
+};
+
+function minutesFromTime(value: string) {
+ const [hour, minute] = value.split(":").map(Number);
+ return hour * 60 + minute;
+}
+
+function dateAtLocalMinutes(day: Date, minutes: number) {
+ const result = new Date(day);
+ result.setUTCHours(0, 0, 0, 0);
+ return addMinutes(result, minutes + 6 * 60);
+}
+
+function windowsForSchedule(schedule: SchedulerSchedule, rangeStart: Date, rangeEnd: Date) {
+ const windows: { start: Date; end: Date; userId: number }[] = [];
+ for (let cursor = new Date(rangeStart); cursor < rangeEnd; cursor = addMinutes(cursor, 24 * 60)) {
+ const day = cursor.getUTCDay();
+ const weekly = schedule.weekly.filter((range) => range.day === day && range.enabled);
+ for (const range of weekly) {
+ windows.push({
+ userId: schedule.userId,
+ start: dateAtLocalMinutes(cursor, minutesFromTime(range.startTime)),
+ end: dateAtLocalMinutes(cursor, minutesFromTime(range.endTime)),
+ });
+ }
+ }
+ return windows;
+}
+
+export function computeMutualSlots(input: ComputeInput): MutualSlot[] {
+ const rangeStart = new Date(input.rangeStart);
+ const rangeEnd = new Date(input.rangeEnd);
+ const attendeeIds = input.schedules.map((schedule) => schedule.userId);
+ const windowsByUser = new Map(
+ input.schedules.map((schedule) => [schedule.userId, windowsForSchedule(schedule, rangeStart, rangeEnd)])
+ );
+ const slots: MutualSlot[] = [];
+
+ for (let cursor = rangeStart; cursor < rangeEnd; cursor = addMinutes(cursor, input.durationMinutes)) {
+ const candidate = { start: cursor, end: addMinutes(cursor, input.durationMinutes) };
+ const allWithinWindows = attendeeIds.every((userId) =>
+ windowsByUser.get(userId)?.some((window) => window.start <= candidate.start && window.end >= candidate.end)
+ );
+ if (!allWithinWindows) continue;
+
+ const conflicts = input.busy.some((block) =>
+ overlaps(candidate, { start: new Date(block.start), end: new Date(block.end) })
+ );
+ if (!conflicts) {
+ slots.push({ start: toIso(candidate.start), end: toIso(candidate.end), attendeeIds });
+ }
+ }
+
+ return slots;
+}
+```
+
+- [ ] **Step 6: Run domain tests**
+
+Run:
+
+```bash
+node .yarn/releases/yarn-4.12.0.cjs workspace @vynte/scheduler test apps/scheduler/lib/scheduler/privacy.test.ts apps/scheduler/lib/scheduler/availability.test.ts
+```
+
+Expected: both tests pass.
+
+- [ ] **Step 7: Commit domain helpers**
+
+```bash
+git add apps/scheduler/lib/scheduler
+git commit -m "Add scheduler availability domain helpers"
+```
+
+## Task 3: Implement Server Service and Scheduler APIs
+
+**Files:**
+- Create: `apps/scheduler/lib/scheduler/legacy-request.ts`
+- Create: `apps/scheduler/lib/scheduler/auth.ts`
+- Create: `apps/scheduler/lib/scheduler/demo-data.ts`
+- Create: `apps/scheduler/lib/scheduler/service.ts`
+- Create: `apps/scheduler/app/api/scheduler/team/route.ts`
+- Create: `apps/scheduler/app/api/scheduler/availability/route.ts`
+- Create: `apps/scheduler/app/api/scheduler/schedule/route.ts`
+- Create: `apps/scheduler/app/api/scheduler/connections/route.ts`
+- Create: `apps/scheduler/app/api/scheduler/meetings/route.ts`
+
+- [ ] **Step 1: Add the legacy request adapter**
+
+Create `apps/scheduler/lib/scheduler/legacy-request.ts`:
+
+```ts
+import type { NextApiRequest } from "next";
+import type { NextRequest } from "next/server";
+
+export function buildLegacyRequest(request: NextRequest): NextApiRequest {
+ const cookies = Object.fromEntries(request.cookies.getAll().map((cookie) => [cookie.name, cookie.value]));
+ const headers = Object.fromEntries(request.headers.entries());
+
+ return {
+ cookies,
+ headers,
+ method: request.method,
+ url: request.nextUrl.pathname + request.nextUrl.search,
+ } as NextApiRequest;
+}
+```
+
+- [ ] **Step 2: Add Authentik session guard**
+
+Create `apps/scheduler/lib/scheduler/auth.ts`:
+
+```ts
+import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
+import type { NextRequest } from "next/server";
+import { NextResponse } from "next/server";
+import { buildLegacyRequest } from "./legacy-request";
+
+export async function getSchedulerSession(request: NextRequest) {
+ return getServerSession({ req: buildLegacyRequest(request) });
+}
+
+export async function requireSchedulerSession(request: NextRequest) {
+ const session = await getSchedulerSession(request);
+ if (!session?.user?.id) {
+ return { response: NextResponse.json({ error: "AUTHENTIK_LOGIN_REQUIRED" }, { status: 401 }) };
+ }
+
+ return { session };
+}
+```
+
+- [ ] **Step 3: Add deterministic demo data for explicit local mode**
+
+Create `apps/scheduler/lib/scheduler/demo-data.ts` with users, schedules, busy blocks, provider categories, and meetings. Export:
+
+```ts
+export const demoUsers = [
+ { id: 1, name: "Zach Sharma", email: "zach@vynte.local", timeZone: "America/Denver" },
+ { id: 2, name: "Maya Chen", email: "maya@vynte.local", timeZone: "America/Denver" },
+ { id: 3, name: "Jordan Lee", email: "jordan@vynte.local", timeZone: "America/Denver" },
+];
+```
+
+The file must set Monday-Friday default weekly ranges and at least three busy blocks.
+
+- [ ] **Step 4: Add service facade**
+
+Create `apps/scheduler/lib/scheduler/service.ts` with these exports:
+
+```ts
+export async function getTeamContext(viewerId: number) {}
+export async function getSchedule(userId: number) {}
+export async function updateSchedule(userId: number, body: unknown) {}
+export async function getConnections(userId: number) {}
+export async function getAvailability(viewerId: number, attendeeIds: number[], rangeStart: string, rangeEnd: string, durationMinutes: number) {}
+export async function listMeetings(viewerId: number) {}
+export async function createMeeting(viewerId: number, body: unknown) {}
+```
+
+Implementation rules:
+
+- If `process.env.SCHEDULER_DEMO_MODE === "1"`, use `demo-data.ts`.
+- Otherwise read users from Prisma and include all unlocked users as the single team.
+- For v1, `createMeeting` validates the attendee ids, organizer inclusion, title, start, and duration, then creates a minimal internal booking record only when the required existing Cal fields can be satisfied. If required Cal booking fields are not available, return `{ status: "not_implemented", reason: "CAL_BOOKING_SERVICE_BOUNDARY" }` with HTTP 501 from the route.
+- `getAvailability` must call `filterBusyBlocksForViewer` and `computeMutualSlots`.
+- `getConnections` must read `Credential` rows and return provider categories without credential secrets.
+
+- [ ] **Step 5: Add API route handlers**
+
+Each route must call `requireSchedulerSession(request)`. If it returns `response`, return that response. Then call the corresponding service method.
+
+Example for `apps/scheduler/app/api/scheduler/team/route.ts`:
+
+```ts
+import { NextResponse, type NextRequest } from "next/server";
+import { requireSchedulerSession } from "@scheduler/lib/scheduler/auth";
+import { getTeamContext } from "@scheduler/lib/scheduler/service";
+
+export async function GET(request: NextRequest) {
+ const auth = await requireSchedulerSession(request);
+ if ("response" in auth) return auth.response;
+
+ return NextResponse.json(await getTeamContext(auth.session.user.id));
+}
+```
+
+Implement `GET` for team, availability, schedule, connections, and meetings. Implement `PUT` for schedule updates and `POST` for meeting creation.
+
+- [ ] **Step 6: Verify API type-check**
+
+Run:
+
+```bash
+node .yarn/releases/yarn-4.12.0.cjs workspace @vynte/scheduler type-check
+```
+
+Expected: PASS.
+
+- [ ] **Step 7: Commit server/API layer**
+
+```bash
+git add apps/scheduler/lib/scheduler apps/scheduler/app/api
+git commit -m "Add scheduler API service layer"
+```
+
+## Task 4: Build Calendar, Availability, and Connections UI
+
+**Files:**
+- Create: `apps/scheduler/components/CalendarWorkspace.tsx`
+- Create: `apps/scheduler/components/AvailabilityEditor.tsx`
+- Create: `apps/scheduler/components/ConnectionsView.tsx`
+- Create: `apps/scheduler/app/calendar/page.tsx`
+- Create: `apps/scheduler/app/availability/page.tsx`
+- Create: `apps/scheduler/app/connections/page.tsx`
+- Modify: `apps/scheduler/app/globals.css`
+
+- [ ] **Step 1: Add calendar workspace client component**
+
+Create `apps/scheduler/components/CalendarWorkspace.tsx` as a `"use client"` component. It must:
+
+- Render a five-day week grid.
+- Render teammate busy blocks without private titles.
+- Render own events with titles.
+- Render mutual slots as outlined green blocks.
+- Keep selected attendee ids in React state.
+- Keep title, duration, optional location, and optional conferencing in React state.
+- POST to `/api/scheduler/meetings` when Create meeting is clicked.
+
+The component props must be:
+
+```ts
+type CalendarWorkspaceProps = {
+ viewerId: number;
+ team: SchedulerUser[];
+ initialBusy: PublicBusyBlock[];
+ initialSlots: MutualSlot[];
+};
+```
+
+- [ ] **Step 2: Add availability editor client component**
+
+Create `apps/scheduler/components/AvailabilityEditor.tsx` as a `"use client"` component. It must:
+
+- Render seven weekly rows.
+- Let each day be enabled/disabled.
+- Let start/end times be edited with native ``.
+- Render overrides from props.
+- PUT updates to `/api/scheduler/schedule`.
+
+- [ ] **Step 3: Add connections server component**
+
+Create `apps/scheduler/components/ConnectionsView.tsx`. It must render:
+
+- Calendars panel.
+- Conferencing panel.
+- Provider rows with `Connected`, `Connect`, or `Browse` status.
+- Privacy note: "Only availability blocks are visible to teammates."
+
+- [ ] **Step 4: Add pages**
+
+Create `apps/scheduler/app/calendar/page.tsx`. It must fetch team and availability server-side by calling service functions directly and render:
+
+```tsx
+
+
+
+```
+
+Create `availability/page.tsx` and `connections/page.tsx` with the same `AppShell` pattern.
+
+- [ ] **Step 5: Extend global CSS**
+
+Add CSS classes matching the approved browser mockups:
+
+- `.calendar-layout`
+- `.calendar-panel`
+- `.week-grid`
+- `.day-column`
+- `.busy-block`
+- `.mutual-slot`
+- `.composer`
+- `.settings-card`
+- `.provider-row`
+- `.primary-button`
+- `.secondary-button`
+
+Use 8px or smaller radius, neutral borders, muted green accents, and stable dimensions. Do not add decorative gradients or marketing copy.
+
+- [ ] **Step 6: Verify UI type-check**
+
+Run:
+
+```bash
+node .yarn/releases/yarn-4.12.0.cjs workspace @vynte/scheduler type-check
+```
+
+Expected: PASS.
+
+- [ ] **Step 7: Commit UI**
+
+```bash
+git add apps/scheduler
+git commit -m "Build Vynte scheduler screens"
+```
+
+## Task 5: Local Run, Browser Verification, and Deployment Hook
+
+**Files:**
+- Modify: `docker-compose.yml`
+- Modify: `portainer.env.example`
+- Create: `apps/scheduler/Dockerfile`
+- Modify: `docs/superpowers/specs/2026-06-14-vynte-scheduler-replacement-design.md` only if verification finds an approved spec correction.
+
+- [ ] **Step 1: Add scheduler Dockerfile**
+
+Create `apps/scheduler/Dockerfile` based on the root Dockerfile pattern but scoped to `@vynte/scheduler`. It must:
+
+- Build from `node:20`.
+- Copy package metadata, `.yarn`, `apps/scheduler`, and `packages`.
+- Run `yarn install`.
+- Run `yarn workspace @vynte/scheduler build`.
+- Expose `3040`.
+- Start with `yarn workspace @vynte/scheduler start`.
+
+- [ ] **Step 2: Add optional Portainer service**
+
+Add a commented `scheduler` service block to `docker-compose.yml` using:
+
+```yaml
+ scheduler:
+ image: ${SCHEDULER_IMAGE:-10.0.3.6:4000/zachariahsharma/vynte-scheduler:latest}
+ restart: unless-stopped
+ ports:
+ - "${SCHEDULER_PORT:-3040}:3040"
+ environment:
+ NODE_ENV: production
+ NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
+ NEXTAUTH_URL: ${NEXTAUTH_URL}
+ DATABASE_URL: postgresql://${POSTGRES_USER:-calcom}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-calcom}
+ DATABASE_DIRECT_URL: postgresql://${POSTGRES_USER:-calcom}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-calcom}
+ CALENDSO_ENCRYPTION_KEY: ${CALENDSO_ENCRYPTION_KEY}
+```
+
+If comments inside the compose service would break parsing, place the service under a `profiles: ["scheduler"]` gate instead of commenting it out.
+
+- [ ] **Step 3: Add env examples**
+
+Add to `portainer.env.example`:
+
+```dotenv
+SCHEDULER_IMAGE=10.0.3.6:4000/zachariahsharma/vynte-scheduler:latest
+SCHEDULER_PORT=3040
+SCHEDULER_DEMO_MODE=0
+```
+
+- [ ] **Step 4: Run tests and build**
+
+Run:
+
+```bash
+node .yarn/releases/yarn-4.12.0.cjs workspace @vynte/scheduler test
+node .yarn/releases/yarn-4.12.0.cjs workspace @vynte/scheduler type-check
+```
+
+Expected: PASS.
+
+- [ ] **Step 5: Start local dev server for Browser verification**
+
+Run:
+
+```bash
+SCHEDULER_DEMO_MODE=1 NEXTAUTH_SECRET=temp node .yarn/releases/yarn-4.12.0.cjs workspace @vynte/scheduler dev
+```
+
+Open `http://localhost:3040/calendar` in Browser. Verify:
+
+- Calendar page renders.
+- Attendee selection changes visible mutual slots.
+- Meeting composer accepts title, duration, optional location, and optional conferencing.
+- Availability page edits weekly hours without layout shift.
+- Connections page renders calendar and conferencing provider panels.
+
+- [ ] **Step 6: Commit deployment hook and verification fixes**
+
+```bash
+git add apps/scheduler docker-compose.yml portainer.env.example
+git commit -m "Add scheduler deployment hook"
+```