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>
This commit is contained in:
parent
778fe1d321
commit
1c9deea3fd
13 changed files with 99 additions and 27 deletions
|
|
@ -1,13 +1,8 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"incremental": false
|
"incremental": false
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"zod": "^3.24.0"
|
"zod": "^3.24.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.0",
|
||||||
"tsup": "^8.3.5",
|
"tsup": "^8.3.5",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"declaration": true,
|
"incremental": false,
|
||||||
"declarationMap": true,
|
"allowImportingTsExtensions": true
|
||||||
"sourceMap": true,
|
|
||||||
"incremental": false
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,18 @@ export type DocumentCardShape = TLBaseShape<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register this shape with tldraw so `BaseBoxShapeUtil<DocumentCardShape>` accepts
|
||||||
|
* it. tldraw's `TLBaseBoxShape = ExtractShapeByProps<{ w, h }>` extracts from
|
||||||
|
* `TLShape`, which only knows about built-in shapes unless we add an entry to
|
||||||
|
* `TLGlobalShapePropsMap` here.
|
||||||
|
*/
|
||||||
|
declare module "@tldraw/tlschema" {
|
||||||
|
interface TLGlobalShapePropsMap {
|
||||||
|
"document-card": DocumentCardShape["props"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function truncatePreview(text: string, max = 100): string {
|
function truncatePreview(text: string, max = 100): string {
|
||||||
const t = text.trim();
|
const t = text.trim();
|
||||||
if (t.length <= max) return t;
|
if (t.length <= max) return t;
|
||||||
|
|
@ -67,7 +79,7 @@ function DocumentCardBody({ shape }: { shape: DocumentCardShape }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DocumentCardShapeUtil extends BaseBoxShapeUtil<any> {
|
export class DocumentCardShapeUtil extends BaseBoxShapeUtil<DocumentCardShape> {
|
||||||
static override type = "document-card";
|
static override type = "document-card";
|
||||||
|
|
||||||
static override props = {
|
static override props = {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@ export type ProjectCardShape = TLBaseShape<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
declare module "@tldraw/tlschema" {
|
||||||
|
interface TLGlobalShapePropsMap {
|
||||||
|
"project-card": ProjectCardShape["props"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function clampProgress(n: number): number {
|
function clampProgress(n: number): number {
|
||||||
if (Number.isNaN(n)) return 0;
|
if (Number.isNaN(n)) return 0;
|
||||||
return Math.min(100, Math.max(0, n));
|
return Math.min(100, Math.max(0, n));
|
||||||
|
|
@ -105,7 +111,7 @@ function ProjectCardBody({ shape }: { shape: ProjectCardShape }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProjectCardShapeUtil extends BaseBoxShapeUtil<any> {
|
export class ProjectCardShapeUtil extends BaseBoxShapeUtil<ProjectCardShape> {
|
||||||
static override type = "project-card";
|
static override type = "project-card";
|
||||||
|
|
||||||
static override props = {
|
static override props = {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ export type TaskCardShape = TLBaseShape<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
declare module "@tldraw/tlschema" {
|
||||||
|
interface TLGlobalShapePropsMap {
|
||||||
|
"task-card": TaskCardShape["props"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const STATUS_DOT: Record<TaskCardShape["props"]["status"], string> = {
|
const STATUS_DOT: Record<TaskCardShape["props"]["status"], string> = {
|
||||||
open: "bg-zinc-400 dark:bg-zinc-500",
|
open: "bg-zinc-400 dark:bg-zinc-500",
|
||||||
in_progress: "bg-blue-500 dark:bg-blue-400",
|
in_progress: "bg-blue-500 dark:bg-blue-400",
|
||||||
|
|
@ -122,7 +128,7 @@ function TaskCardBody({ shape }: { shape: TaskCardShape }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TaskCardShapeUtil extends BaseBoxShapeUtil<any> {
|
export class TaskCardShapeUtil extends BaseBoxShapeUtil<TaskCardShape> {
|
||||||
static override type = "task-card";
|
static override type = "task-card";
|
||||||
|
|
||||||
static override props = {
|
static override props = {
|
||||||
|
|
|
||||||
19
apps/web/eslint.config.mjs
Normal file
19
apps/web/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { dirname } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = [
|
||||||
|
{
|
||||||
|
ignores: [".next/**", "node_modules/**", "dist/**", "next-env.d.ts"],
|
||||||
|
},
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "eslint .",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -77,7 +77,9 @@
|
||||||
"zustand": "^5.0.0"
|
"zustand": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.3.5",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"@tldraw/tlschema": "4.5.4",
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import type { PgTable } from "drizzle-orm/pg-core";
|
||||||
import {
|
import {
|
||||||
objects,
|
objects,
|
||||||
forms,
|
forms,
|
||||||
|
|
@ -16,6 +17,11 @@ type Db = typeof defaultDb;
|
||||||
* routers when a mutation targets a specific row by id. Throws NOT_FOUND if the
|
* routers when a mutation targets a specific row by id. Throws NOT_FOUND if the
|
||||||
* row either doesn't exist or lives in a different workspace, so callers can't
|
* row either doesn't exist or lives in a different workspace, so callers can't
|
||||||
* use the error code to probe IDs across tenants.
|
* use the error code to probe IDs across tenants.
|
||||||
|
*
|
||||||
|
* The `table` parameter is constrained structurally to having `id` and
|
||||||
|
* `workspaceId` columns; the `as PgTable` cast inside the body is the
|
||||||
|
* standard drizzle escape hatch when a structural generic table can't be
|
||||||
|
* proven equivalent to drizzle's nominal `PgTable` shape.
|
||||||
*/
|
*/
|
||||||
export async function assertRowInWorkspace<
|
export async function assertRowInWorkspace<
|
||||||
T extends { id: typeof objects.id; workspaceId: typeof objects.workspaceId },
|
T extends { id: typeof objects.id; workspaceId: typeof objects.workspaceId },
|
||||||
|
|
@ -28,7 +34,7 @@ export async function assertRowInWorkspace<
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const [row] = await args.db
|
const [row] = await args.db
|
||||||
.select({ id: args.table.id })
|
.select({ id: args.table.id })
|
||||||
.from(args.table as any)
|
.from(args.table as unknown as PgTable)
|
||||||
.where(and(eq(args.table.id, args.rowId), eq(args.table.workspaceId, args.workspaceId)))
|
.where(and(eq(args.table.id, args.rowId), eq(args.table.workspaceId, args.workspaceId)))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
if (!row) {
|
if (!row) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { randomUUID } from "crypto";
|
/**
|
||||||
|
* Web-Crypto based UUID v4 generator. We use `globalThis.crypto.randomUUID`
|
||||||
|
* rather than Node's `node:crypto` because `@tasks/shared` is meant to be
|
||||||
|
* importable from both browser and server bundles (see `AGENTS.md` §4 and
|
||||||
|
* `.cursor/rules/repo-overview.mdc`). The Web Crypto API is available in
|
||||||
|
* Node >= 20 and every modern browser, so this works in both worlds.
|
||||||
|
*/
|
||||||
export function generateId(): string {
|
export function generateId(): string {
|
||||||
return randomUUID();
|
return globalThis.crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ slug: fix-lint-and-shared-types
|
||||||
title: Fix pnpm lint (Next 16 deprecation) and packages/shared Node-API leak
|
title: Fix pnpm lint (Next 16 deprecation) and packages/shared Node-API leak
|
||||||
plan_slug: daily-driver-finish
|
plan_slug: daily-driver-finish
|
||||||
epic_slug: shipping-the-shell
|
epic_slug: shipping-the-shell
|
||||||
status: ready
|
status: done
|
||||||
priority: P0
|
priority: P0
|
||||||
tenant_id: global
|
tenant_id: global
|
||||||
owner: unassigned
|
owner: unassigned
|
||||||
cursor_todo_id: null
|
cursor_todo_id: null
|
||||||
updated_at: "2026-06-01"
|
updated_at: "2026-06-02"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Task summary
|
# Task summary
|
||||||
|
|
@ -48,11 +48,26 @@ Verify by grepping for other Node imports in `packages/shared/` while you're in
|
||||||
|
|
||||||
## Subtasks
|
## Subtasks
|
||||||
|
|
||||||
- [ ] Run the Next.js codemod and verify `apps/web/eslint.config.{js,mjs}` is created.
|
- [x] Create `apps/web/eslint.config.mjs` using `FlatCompat` from `@eslint/eslintrc` (the codemod assumes an existing config; we had none).
|
||||||
- [ ] Update `apps/web/package.json` `lint` script.
|
- [x] Update `apps/web/package.json` `lint` script from `next lint` to `eslint .`.
|
||||||
- [ ] Replace `crypto.randomUUID` with `globalThis.crypto.randomUUID` in `packages/shared/src/utils/id.ts`.
|
- [x] Replace `crypto.randomUUID` with `globalThis.crypto.randomUUID` in `packages/shared/src/utils/id.ts`.
|
||||||
- [ ] Grep `packages/shared/src/` for other Node-only imports and fix.
|
- [x] Grep `packages/shared/src/` for other Node-only imports — none found beyond `crypto`.
|
||||||
- [ ] Verify `pnpm lint && pnpm type-check` exits 0.
|
- [x] Verify `pnpm lint && pnpm type-check` exits 0.
|
||||||
|
|
||||||
|
### Adjacent fixes pulled in to make `lint && type-check` actually pass
|
||||||
|
|
||||||
|
Lint failing on the interactive prompt was hiding several real type/lint errors. Once lint was running, the following pre-existing issues had to be resolved before `pnpm lint` exited 0:
|
||||||
|
|
||||||
|
- `apps/mcp-server/tsconfig.json`: removed vestigial `rootDir`/`declaration`/`outDir`/`sourceMap`/`declarationMap` and added `allowImportingTsExtensions: true`. The MCP server uses `.ts`-extension re-export shims (see `apps/mcp-server/src/db.ts`, `schema.ts`, `shared-types.ts`) to let tsup bundle workspace `.ts` sources at build time. With `rootDir` set, cross-workspace imports tripped TS6059; with `noEmit` inherited from root, the other fields were unused.
|
||||||
|
- `apps/collab-server/tsconfig.json`: same simplification.
|
||||||
|
- `apps/mcp-server/package.json`: added `@types/node` so `process.env` in `packages/database/src/client.ts` (pulled in transitively) resolves.
|
||||||
|
- `apps/web/components/ui/input.tsx`: replaced empty `interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}` with `type InputProps = React.InputHTMLAttributes<HTMLInputElement>`.
|
||||||
|
- `apps/web/server/lib/workspace-guard.ts`: replaced `as any` on the `from(...)` call with `as unknown as PgTable` and documented why drizzle's structural generic needs the escape hatch.
|
||||||
|
- Three whiteboard shapes (`document-card`, `project-card`, `task-card`): replaced `BaseBoxShapeUtil<any>` with the properly-typed `BaseBoxShapeUtil<DocumentCardShape>` / etc., plus module augmentation on `TLGlobalShapePropsMap` from `@tldraw/tlschema` so tldraw's `TLBaseBoxShape` extracts our custom shapes. Required adding `@tldraw/tlschema` as a direct devDep of `apps/web` so the `declare module` could resolve.
|
||||||
|
|
||||||
|
### Warnings remaining (out of scope, not blocking lint pass)
|
||||||
|
|
||||||
|
`pnpm lint` reports 16 warnings — all pre-existing unused imports, unused vars, and `react-hooks/exhaustive-deps` notes. Errors are gone; these are housekeeping for a future task.
|
||||||
|
|
||||||
## Owner or assignee
|
## Owner or assignee
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,9 @@ importers:
|
||||||
specifier: ^3.24.0
|
specifier: ^3.24.0
|
||||||
version: 3.25.76
|
version: 3.25.76
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.10.0
|
||||||
|
version: 22.19.15
|
||||||
tsup:
|
tsup:
|
||||||
specifier: ^8.3.5
|
specifier: ^8.3.5
|
||||||
version: 8.5.1(jiti@1.21.7)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
version: 8.5.1(jiti@1.21.7)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||||
|
|
@ -281,9 +284,15 @@ importers:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
version: 5.0.12(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@eslint/eslintrc':
|
||||||
|
specifier: ^3.3.5
|
||||||
|
version: 3.3.5
|
||||||
'@tailwindcss/typography':
|
'@tailwindcss/typography':
|
||||||
specifier: ^0.5.16
|
specifier: ^0.5.16
|
||||||
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||||
|
'@tldraw/tlschema':
|
||||||
|
specifier: 4.5.4
|
||||||
|
version: 4.5.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.10.0
|
specifier: ^22.10.0
|
||||||
version: 22.19.15
|
version: 22.19.15
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue