Commit graph

4 commits

Author SHA1 Message Date
Randall Stillwell
58f92f3898 feat(security): in-process rate limit for sign-in and invite creation
Algorithm: a fixed-window token bucket implemented as a pure function in
`@tasks/shared` (`consumeTokenBucket`) plus a thin `apps/web` wrapper that
holds per-key state in a module-scoped `Map`. No Redis, no external deps —
horizontally-scaled deploys will need a Redis-backed swap behind the same
`rateLimit()` signature; called out in the JSDoc as a follow-up. The pure
core is unit-tested in `packages/shared` (6 new vitest cases covering
allow/deny, window reset, key isolation, monotonic retryAfterMs, denied-
flood pegging, and option validation); the wrapper is intentionally not
tested here because apps/web has no vitest harness yet.

Wire-ins (the two narrow surfaces called out in the v1 spec):

  1. Credentials `authorize` in `apps/web/lib/auth.ts`: 5 attempts per
     IP per 60s. IP comes from `next/headers` (x-forwarded-for first
     entry, then x-real-ip); when headers() throws or returns nothing we
     fall back to keying on "unknown" in prod and skipping the limiter
     entirely in dev so a local test loop doesn't lock itself out. On a
     trip we `console.warn` and return null — the standard Auth.js
     "auth failed" signal — without consulting the DB.

  2. `invites.create` in `apps/web/server/routers/invites.ts`: 10
     invite-creates per inviter per hour. Keyed by inviter id (not
     workspace) so a multi-workspace admin can't multiply their
     allowance. On trip we throw TRPCError TOO_MANY_REQUESTS with a
     retry-after seconds count baked into the message.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 13:26:19 -05:00
Randall Stillwell
48defb2ffd test(ci): bootstrap vitest + GitHub Actions and lock in 3-gate quality bar
First Path-B task. Path A landed daily-driver features without a
test runner; Path B is "harden so the next batch of changes can't
silently regress what just shipped." Step 1 is making `pnpm test`
real and gating CI on it.

Test runner:
* Install vitest + @vitest/coverage-v8 at the workspace root.
* Add vitest.config.ts (environment: "node", no JSDOM) + test /
  test:watch scripts to packages/shared, packages/database,
  packages/ai. Wire `test` into turbo.json with dependsOn: ^build
  for future-proofing; add `pnpm test` to root package.json.

Three real tests (no snapshot theater — verified by mutation):
* packages/shared/src/utils/id.test.ts: asserts generateId() matches
  the RFC 4122 v4 regex and produces 1000 distinct values. Mutating
  generateId() to a constant fails both assertions.
* packages/shared/src/types/objects.test.ts: pins objectTypes and
  objectStatuses arrays. These back the zod enum on objects.create
  and the "open tasks" count on the workspace-home dashboard; a
  silent reorder/rename would otherwise corrupt the dashboard math.
* packages/database/src/markdown-backlog/parse.test.ts: covers
  parseBacklogMarkdown across three shapes (well-formed Task,
  no-frontmatter Plan with path inference, malformed YAML that
  must NOT throw — the importer runs in a file watcher). Plus
  hashFileContents determinism.
* packages/ai/src/actions/index.test.ts: five tests across the
  prompt builders (summarize/expand/rewrite × 3 tones / translate /
  generateFromPrompt). Pure functions; no model mocking needed.

CI:
* .github/workflows/ci.yml runs on pull_request and push to main.
  Node 20, pnpm 9 pinned explicitly (per AGENTS.md). Uses
  setup-node's built-in pnpm cache. Steps: install --frozen-lockfile,
  lint, type-check, test. Concurrency group cancels superseded runs
  on non-main branches.

Docs:
* AGENTS.md: drop the "no test runner configured" disclaimer.
  Document pnpm test / test:watch. Update the PR-readiness rule
  from `pnpm lint && pnpm type-check` to
  `pnpm lint && pnpm type-check && pnpm test`.

All 14 tests pass; lint + type-check still green across all 6
packages. The CI workflow's first run is gated on the operator
pushing this branch — that's the only acceptance criterion left
unverified in this commit.

Closes plans/Plan-multitenant-saas-hardening/Epic-test-foundation/
Task-bootstrap-vitest-and-ci.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 09:23:55 -05:00
Randall Stillwell
1c9deea3fd chore(lint+types): green pnpm lint && pnpm type-check from a clean clone
Path-A first task: get the repo's two repo-wide quality gates passing.
Both were failing from a clean clone in ways that were silently hiding
each other.

Headline fixes:

* apps/web: add an eslint 9 flat config (eslint.config.mjs) using
  FlatCompat against next/core-web-vitals + next/typescript, and switch
  the `lint` script from `next lint` to `eslint .`. Previously `next
  lint` fell into its interactive setup prompt because there was no
  config at all in apps/web, which made `pnpm lint` permanently fail
  before any rule ever ran.
* packages/shared/src/utils/id.ts: replace `randomUUID` from `node:crypto`
  with `globalThis.crypto.randomUUID`. `@tasks/shared` is forbidden from
  using Node-only APIs (per AGENTS.md / repo-overview.mdc) because it
  has to be importable from the browser bundle.

Adjacent fixes pulled in to make the gates actually green:

* apps/mcp-server/tsconfig.json: drop vestigial rootDir / declaration*
  / outDir / sourceMap (build is via tsup, not tsc emit) and add
  allowImportingTsExtensions. The MCP server uses `.ts`-extension
  re-export shims (db.ts / schema.ts / shared-types.ts) so tsup can
  inline workspace .ts sources into the bundle.
* apps/collab-server/tsconfig.json: same simplification.
* apps/mcp-server/package.json: add @types/node so `process.env` in
  packages/database/src/client.ts (transitively pulled into the MCP
  server's type-check) resolves.
* apps/web/components/ui/input.tsx: empty `interface InputProps extends
  React.InputHTMLAttributes<HTMLInputElement> {}` -> `type` alias.
* apps/web/server/lib/workspace-guard.ts: `from(args.table as any)` ->
  `as unknown as PgTable` with a comment. Standard drizzle escape
  hatch for structural generic tables.
* apps/web/components/whiteboard/shapes/{document,project,task}-card.tsx:
  `BaseBoxShapeUtil<any>` -> `BaseBoxShapeUtil<{Shape}>` plus inline
  `declare module "@tldraw/tlschema"` augmentation of
  TLGlobalShapePropsMap. Required adding @tldraw/tlschema as a direct
  devDep of apps/web so the augmentation target resolves; previously
  it was only present transitively under tldraw's own deps.

Result: `pnpm lint && pnpm type-check` exits 0 across all 6 packages.
16 unused-import / exhaustive-deps warnings remain; they're pre-existing
housekeeping and out of scope for this task.

Closes plans/Plan-daily-driver-finish/Epic-shipping-the-shell/
Task-fix-lint-and-shared-types.md (status: done).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-02 00:11:52 -05:00
Randall Stillwell
a508ece6e7 feat: Full project management application scaffold
Complete architecture for a ClickUp/Notion/Miro-class project management app:

- Turborepo monorepo with Next.js 15, TypeScript, PostgreSQL (Drizzle ORM)
- Object-centered database schema (everything is an Object: tasks, projects, docs, whiteboards)
- NextAuth v5 authentication with credentials + OAuth providers
- tRPC v11 API layer with full CRUD for objects, properties, relations, templates, search
- Three-panel UI: collapsible sidebar, center content area, push-in right panel
- Purple/teal theme with light/dark mode via Shadcn/ui + Tailwind CSS
- Multiple views: List, Kanban board (dnd-kit), Table (spreadsheet), Embedded iframe
- TipTap rich text editor with slash commands, custom blocks (callout, toggle, mention, embed, divider), AI block
- Real-time collaboration via Yjs + Hocuspocus with presence/cursors
- tldraw whiteboard with custom shape cards (task, document, project)
- MCP server exposing all app data/tools for AI agents
- AI chat panel, editor AI slash commands, Cmd+K command palette
- Template system with built-in templates (Bug Report, Meeting Notes, Sprint)
- Full-text search with result highlighting
- Docker Compose for full-stack deployment (web + collab + postgres + redis)

Made-with: Cursor
2026-03-26 22:39:16 -05:00