feat: ship runnable self-hosted demo - #3
Conversation
There was a problem hiding this comment.
Pull request overview
This PR pivots the repo from the legacy v0.1 macOS CLI/TUI implementation to a runnable self-hosted demo built as a pnpm monorepo: @schedyx/core (deterministic scheduler), @schedyx/api (Hono + SQLite + scheduler daemon), and @schedyx/web (Next.js dashboard consuming the live API).
Changes:
- Remove legacy CLI/TUI/AppleScript adapter code and root-level TS/Vitest config, replacing with per-package configs.
- Add runnable demo stack: SQLite persistence + seeded fixtures + scheduler cycle + Hono routes + Next.js dashboard with a manual reschedule action.
- Add Dockerfiles/Compose wiring and refresh docs to reflect current architecture and usage.
Reviewed changes
Copilot reviewed 92 out of 104 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.config.ts | Removed legacy root Vitest config (moved to package-level). |
| tsconfig.json | Removed legacy root TS config (moved to package-level). |
| src/tui/utils.ts | Removed legacy Ink TUI utility module. |
| src/tui/types.ts | Removed legacy Ink TUI types. |
| src/tui/index.tsx | Removed legacy Ink TUI entrypoint. |
| src/tui/components/TaskQueue.tsx | Removed legacy Ink TUI component. |
| src/tui/components/StatusBar.tsx | Removed legacy Ink TUI component. |
| src/tui/components/Section.tsx | Removed legacy Ink TUI component. |
| src/tui/components/HelpScreen.tsx | Removed legacy Ink TUI component. |
| src/tui/components/BlockList.tsx | Removed legacy Ink TUI component. |
| src/tui/actions.ts | Removed legacy TUI action layer. |
| src/tui/App.tsx | Removed legacy Ink TUI app. |
| src/cli/index.ts | Removed legacy CLI entrypoint. |
| src/cli/commands/tui.ts | Removed legacy CLI TUI command. |
| src/cli/commands/reconcile.ts | Removed legacy CLI reconcile command. |
| src/cli/commands/plan.ts | Removed legacy CLI plan command. |
| src/cli/commands/init.ts | Removed legacy CLI init wizard. |
| src/cli/commands/commands.test.ts | Removed legacy CLI command tests. |
| src/cli/commands/clear.ts | Removed legacy CLI clear command. |
| src/cli/commands/check.ts | Removed legacy CLI check command. |
| src/cli/commands/apply.ts | Removed legacy CLI apply command. |
| src/adapters/reminders/reminders-applescript.ts | Removed legacy AppleScript Reminders adapter. |
| src/adapters/index.ts | Removed legacy adapter resolver. |
| src/adapters/calendar/applescript.ts | Removed legacy AppleScript Calendar adapter. |
| src/adapters/applescript-adapters.test.ts | Removed legacy AppleScript adapter tests. |
| pnpm-workspace.yaml | Added pnpm workspace definition and native-build allowlist. |
| packages/web/tsconfig.json | Added Next.js/React TS config for web package. |
| packages/web/src/lib/api.ts | Added typed fetch helpers for demo API. |
| packages/web/src/components/task-queue.tsx | Added task queue UI component. |
| packages/web/src/components/schedule-list.tsx | Added schedule list UI component. |
| packages/web/src/components/reschedule-button.tsx | Added client-side reschedule trigger. |
| packages/web/src/app/page.tsx | Added demo dashboard page consuming API. |
| packages/web/src/app/layout.tsx | Added root layout + metadata. |
| packages/web/src/app/globals.css | Added demo styling. |
| packages/web/package.json | Added web package manifest/scripts/deps. |
| packages/web/next.config.ts | Configured output tracing root for monorepo deploy. |
| packages/web/next-env.d.ts | Added Next.js TS env types. |
| packages/web/Dockerfile.dev | Added dev Dockerfile for web. |
| packages/web/Dockerfile | Added prod Dockerfile for web. |
| packages/web/.eslintrc.cjs | Added web ESLint config extending root + Next rules. |
| packages/core/vitest.config.ts | Added core package Vitest config. |
| packages/core/tsconfig.json | Added core package TS config (emit declarations/maps). |
| packages/core/src/validation.ts | Added shared validation/sanitization helpers. |
| packages/core/src/time.ts | Added timezone + local date/time formatting helpers. |
| packages/core/src/reconcile.ts | Adjusted config import to type-only. |
| packages/core/src/reconcile.test.ts | Added reconcile unit tests in core package. |
| packages/core/src/planner.ts | Adjusted config import to type-only. |
| packages/core/src/planner.test.ts | Added planner unit tests in core package. |
| packages/core/src/notes.ts | Added #autoblock note helpers. |
| packages/core/src/models.ts | Added shared domain model types. |
| packages/core/src/locking.ts | Added locking logic to flip free based on lockAt. |
| packages/core/src/locking.test.ts | Added locking unit tests in core package. |
| packages/core/src/index.ts | Added public exports for core package. |
| packages/core/src/config.ts | Added shared config type. |
| packages/core/package.json | Added core package manifest/scripts/exports. |
| packages/api/vitest.config.ts | Added API package Vitest config. |
| packages/api/tsconfig.json | Added API package TS config. |
| packages/api/src/services/scheduler-service.ts | Added scheduler cycle + snapshot helpers (core integration + persistence). |
| packages/api/src/services/scheduler-service.test.ts | Added scheduler service unit test. |
| packages/api/src/scheduler/daemon.ts | Added cron-based scheduler daemon. |
| packages/api/src/routes/tasks.ts | Added GET /tasks route. |
| packages/api/src/routes/schedule.ts | Added schedule snapshot + manual run routes. |
| packages/api/src/routes/health.ts | Added health route. |
| packages/api/src/mock/seed-demo-data.ts | Added seed-on-empty + fixture rebasing. |
| packages/api/src/mock/seed-demo-data.test.ts | Added seed/rebase unit test. |
| packages/api/src/mock/demo-fixtures.ts | Added demo task/event fixtures. |
| packages/api/src/index.ts | Added API server bootstrap + seeding + daemon startup. |
| packages/api/src/env.ts | Added env parsing for server/db/scheduler settings. |
| packages/api/src/db/schema.ts | Added SQLite schema for demo state. |
| packages/api/src/db/repositories/tasks-repository.ts | Added task persistence/query helpers. |
| packages/api/src/db/repositories/events-repository.ts | Added event persistence/query helpers. |
| packages/api/src/db/repositories/blocks-repository.ts | Added block persistence + meta storage helpers. |
| packages/api/src/db/client.ts | Added better-sqlite3 client + schema init. |
| packages/api/src/db/client.test.ts | Added DB initialization + repository round-trip test. |
| packages/api/src/app.ts | Added Hono app wiring for routes. |
| packages/api/src/app.test.ts | Added API route integration tests. |
| packages/api/package.json | Added API package manifest/scripts/deps. |
| packages/api/Dockerfile.dev | Added dev Dockerfile for API. |
| packages/api/Dockerfile | Added prod Dockerfile for API. |
| package.json | Converted root to monorepo scripts/devDeps and bumped version. |
| docs/superpowers/plans/2026-03-24-v0-3-runnable-self-hosted-demo.md | Added detailed implementation plan doc for v0.3 demo. |
| docs/VISION.md | Added product vision document for new direction. |
| docs/TECH_SPEC.md | Marked legacy TECH_SPEC and added pointers to current docs. |
| docs/STATUS.md | Rewrote status doc for runnable demo baseline. |
| docs/ROADMAP.md | Rewrote roadmap starting post-v0.3 demo baseline. |
| docs/QUICKSTART.md | Marked legacy quickstart guide. |
| docs/PHASE0.md | Added historical monorepo migration record. |
| docs/ONBOARDING.md | Marked legacy onboarding doc. |
| docs/FEATURES.md | Marked legacy features doc. |
| docs/EXECUTION_PLAN.md | Marked legacy execution plan doc. |
| docs/DEVELOPMENT.md | Updated dev docs for monorepo + API/web demo + Docker flows. |
| docs/CHANGELOG.md | Updated changelog framing around monorepo and demo milestones. |
| docs/ARCHITECTURE.md | Updated architecture doc to reflect new package layout and demo flows. |
| docker-compose.yml | Added runnable prod-style compose for API + web. |
| docker-compose.dev.yml | Added runnable dev-style compose with source mounts. |
| README.md | Updated README for self-hosted demo and current package layout. |
| CLAUDE.md | Added AI session context doc. |
| AGENTS.md | Added AI session context doc (duplicate content). |
| .gitignore | Updated ignore rules for Next/TS/pnpm artifacts. |
| .github/workflows/ci.yml | Updated CI to Ubuntu + pnpm + core build/test + lint/typecheck. |
| .eslintrc.cjs | Enabled JSX parsing and ignored .next/. |
| .dockerignore | Added Docker ignore rules for monorepo artifacts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function createApp(db: SqliteDatabase) { | ||
| const app = new Hono(); | ||
|
|
||
| registerHealthRoute(app); | ||
| registerTaskRoutes(app, db); | ||
| registerScheduleRoutes(app, db); |
There was a problem hiding this comment.
The web dashboard triggers POST /schedule/run from a client component (browser) on http://localhost:3000 to the API on http://localhost:3001. The API currently doesn’t set CORS headers, so browsers will block this request (preflight/Origin mismatch). Add explicit CORS handling in the API (e.g., Hono CORS middleware) to allow the web origin(s) used in dev and Docker, or alternatively proxy the call through the Next.js app to keep it same-origin.
| onClick={() => { | ||
| startTransition(async () => { | ||
| await fetch(`${apiBase}/schedule/run`, { method: "POST" }); | ||
| router.refresh(); | ||
| }); |
There was a problem hiding this comment.
The reschedule click handler doesn’t check response.ok (or handle exceptions) before calling router.refresh(), so failures (including CORS/network errors) will be silent and leave the UI stale. Consider surfacing an error state and only refreshing after a successful POST, or at least guarding with an if (!res.ok) path.
| export function readScheduleSnapshot(db: SqliteDatabase, now: Date = new Date()) { | ||
| return { | ||
| date: now.toISOString().slice(0, 10), | ||
| blocks: listBlocks(db), |
There was a problem hiding this comment.
readScheduleSnapshot computes date via now.toISOString().slice(0, 10) (UTC date), but runSchedulerCycle returns plan.date which is derived from local date logic in @schedyx/core (formatLocalDate). These can diverge around midnight/timezones and make /schedule/today and /schedule/run disagree. Use the same date derivation as the planner (e.g., formatLocalDate(now) from @schedyx/core) so both endpoints report a consistent “today”.
| export function readEnv() { | ||
| return { | ||
| host: process.env.HOST ?? "0.0.0.0", | ||
| port: Number(process.env.PORT ?? "3001"), | ||
| databasePath: process.env.DATABASE_PATH ?? "./schedyx-demo.db", | ||
| schedulerIntervalMinutes: Number(process.env.SCHEDULER_INTERVAL_MINUTES ?? "15"), | ||
| }; |
There was a problem hiding this comment.
readEnv parses numeric env vars with Number(...) but doesn’t handle NaN (e.g., PORT=abc, SCHEDULER_INTERVAL_MINUTES=abc). That can propagate into server startup and cron scheduling and fail at runtime. Consider validating/clamping here (defaulting when Number.isFinite(...) is false) so invalid env doesn’t crash the demo.
| export function startSchedulerDaemon(db: SqliteDatabase, intervalMinutes: number) { | ||
| const safeInterval = Math.max(1, intervalMinutes); | ||
| return cron.schedule(`*/${safeInterval} * * * *`, () => { | ||
| runSchedulerCycle(db, new Date()); | ||
| }); |
There was a problem hiding this comment.
safeInterval can become NaN if intervalMinutes is NaN, and values >59 produce an invalid cron minute field (e.g., */90 * * * *). This will make cron.schedule(...) throw or misbehave. Clamp/validate intervalMinutes (finite, 1–59) and fall back to a default when invalid.
| export function getLastUnscheduled(db: SqliteDatabase): Task[] { | ||
| const row = db | ||
| .prepare("SELECT value FROM app_meta WHERE key = ?") | ||
| .get(LAST_UNSCHEDULED_KEY) as { value?: string } | undefined; | ||
|
|
||
| if (!row?.value) { | ||
| return []; | ||
| } | ||
|
|
||
| return JSON.parse(row.value) as Task[]; | ||
| } |
There was a problem hiding this comment.
getLastUnscheduled does a raw JSON.parse of the persisted meta value. If the DB is corrupted or an old value is present, this will throw and can break /schedule/today and /schedule/run. Wrap parsing in a try/catch (return [] on failure) and consider validating the decoded shape before returning it as Task[].
| packages: | ||
| - "packages/*" | ||
| allowBuilds: | ||
| better-sqlite3: true | ||
| esbuild: true | ||
| sharp: true | ||
| unrs-resolver: true |
There was a problem hiding this comment.
allowBuilds is only supported in pnpm >=10.26.0; with earlier pnpm v10 releases (and pnpm v9), it will be ignored, which can reintroduce install failures for native deps like better-sqlite3. Either switch this to onlyBuiltDependencies (array) for broader compatibility, or pin pnpm in CI/dev images to >=10.26.0 to guarantee allowBuilds is honored.
Summary
Test Plan