From a78bb1d056053e7efc4a02afdde6a64578fc753a Mon Sep 17 00:00:00 2001 From: CrewCircle Date: Sun, 26 Jul 2026 23:15:42 +1000 Subject: [PATCH] =?UTF-8?q?Fix=20production=20build=20failure=20=E2=80=94?= =?UTF-8?q?=20every=20Vercel=20deployment=20has=20been=20erroring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple stacked bugs, found by reproducing `npm run build` locally since Turbopack dev mode doesn't enforce the same checks as a production build: - packages/knowledge/tsconfig.json had noEmit:true, so its own `build` script never emitted dist/*.js despite exiting 0. Root package.json never built it either. Added a root `prebuild` script and fixed the package.json exports map (`import` conditions pointed at dist/index.mjs, a file the build never produced — corrected to dist/esm/index.js to match actual tsc output). - Root tsconfig.json's `include: ["**/*.ts"]` pulled every workspace package's source into the website's own typecheck, so an unrelated missing dep in packages/observability failed the whole build. Excluded packages/* — each package already has its own tsconfig. - lucide-react dropped brand icons in the installed v1.26; `Github` no longer exists. Swapped for GitFork (label text already says "GitHub"). - admin/knowledge/page.tsx called a static InternalMemory.create() that was never part of the class's API — fixed to `new InternalMemory()` + `.initialize()`, matching the class's actual constructor/method. - DataTable generic wasn't inferred correctly from JSX props in two admin tables; added explicit `>` type arguments and the index signatures its `T extends Record` constraint requires. - API routes catching the plain `Response` requireAdmin() throws and returning it directly, typed as NextResponse — wrapped in `new NextResponse(...)` across all 6 affected routes. - apps.ts and content.ts's FOOTER_LINKS had regressed to a stale, wrong app lineup (missing TaxFlowAI/LocalMate entirely, a fabricated "XeroAssist" entry that was never a real product) from a bad merge — confirmed via git history against a commit explicitly tagged "Build verified" and cross-checked against the admin registry's actual provisioned projects. Restored the correct data. Verified with a full local `npm run build` (production Turbopack build, not dev mode) plus a `next start` smoke test of /, /admin, and the other admin routes. Co-Authored-By: Claude Sonnet 5 --- package.json | 1 + packages/knowledge/package.json | 12 +- packages/knowledge/tsconfig.json | 1 - packages/knowledge/vitest.config.ts | 1 - src/app/admin/costs/fixed-costs-table.tsx | 14 +-- src/app/admin/knowledge/page.tsx | 5 +- src/app/admin/projects-table.tsx | 6 +- src/app/admin/projects/[id]/page.tsx | 4 +- src/app/api/admin/costs/fixed/route.ts | 4 +- src/app/api/admin/costs/route.ts | 4 +- src/app/api/admin/observability/llm/route.ts | 4 +- .../api/admin/observability/sentry/route.ts | 4 +- .../api/admin/observability/uptime/route.ts | 4 +- src/app/api/admin/projects/route.ts | 4 +- src/lib/admin/costs.ts | 1 + src/lib/config/apps.ts | 105 ++++++++++++------ src/lib/config/content.ts | 13 +-- tsconfig.json | 2 +- 18 files changed, 116 insertions(+), 73 deletions(-) diff --git a/package.json b/package.json index e48abd0..37865ab 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ ], "scripts": { "dev": "next dev", + "prebuild": "npm run build --workspace=@crewcircle/knowledge", "build": "next build", "start": "next start", "lint": "eslint" diff --git a/packages/knowledge/package.json b/packages/knowledge/package.json index b1fe5a3..9840911 100644 --- a/packages/knowledge/package.json +++ b/packages/knowledge/package.json @@ -3,32 +3,32 @@ "version": "0.1.0", "description": "CrewCircle Knowledge Layer - Internal Org Memory & External App Context Layer", "main": "dist/index.js", - "module": "dist/index.mjs", + "module": "dist/esm/index.js", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", + "import": "./dist/esm/index.js", "require": "./dist/index.js" }, "./core": { "types": "./dist/core/index.d.ts", - "import": "./dist/core/index.mjs", + "import": "./dist/esm/core/index.js", "require": "./dist/core/index.js" }, "./providers": { "types": "./dist/providers/index.d.ts", - "import": "./dist/providers/index.mjs", + "import": "./dist/esm/providers/index.js", "require": "./dist/providers/index.js" }, "./internal": { "types": "./dist/internal/index.d.ts", - "import": "./dist/internal/index.mjs", + "import": "./dist/esm/internal/index.js", "require": "./dist/internal/index.js" }, "./external": { "types": "./dist/external/index.d.ts", - "import": "./dist/external/index.mjs", + "import": "./dist/esm/external/index.js", "require": "./dist/external/index.js" }, "./ai-sdk": { diff --git a/packages/knowledge/tsconfig.json b/packages/knowledge/tsconfig.json index 6b2f6dd..d7679d5 100644 --- a/packages/knowledge/tsconfig.json +++ b/packages/knowledge/tsconfig.json @@ -10,7 +10,6 @@ "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "isolatedModules": true, - "noEmit": true, "declaration": true, "declarationMap": true, "sourceMap": true, diff --git a/packages/knowledge/vitest.config.ts b/packages/knowledge/vitest.config.ts index ed2135f..4aebf2f 100644 --- a/packages/knowledge/vitest.config.ts +++ b/packages/knowledge/vitest.config.ts @@ -20,5 +20,4 @@ export default defineConfig({ '@crewcircle/knowledge': path.resolve(__dirname, './src/index.ts'), }, }, - tsconfig: './tsconfig.json', }); \ No newline at end of file diff --git a/src/app/admin/costs/fixed-costs-table.tsx b/src/app/admin/costs/fixed-costs-table.tsx index 5094f36..13543d9 100644 --- a/src/app/admin/costs/fixed-costs-table.tsx +++ b/src/app/admin/costs/fixed-costs-table.tsx @@ -1,17 +1,11 @@ "use client"; import { DataTable } from "@crewcircle/admin-ui"; - -interface FixedCostItem { - name: string; - category: string; - provider: string; - amount_cents: number; - frequency: string; -} +import type { DataTableColumn } from "@crewcircle/admin-ui"; +import type { FixedCostItem } from "@/lib/admin/costs"; export function FixedCostsTable({ items }: { items: FixedCostItem[] }) { - const columns = [ + const columns: DataTableColumn[] = [ { key: "name" as const, header: "Name", sortable: true }, { key: "category" as const, header: "Category", sortable: true }, { key: "provider" as const, header: "Provider", sortable: true }, @@ -30,7 +24,7 @@ export function FixedCostsTable({ items }: { items: FixedCostItem[] }) { ]; return ( - columns={columns} rows={items} emptyMessage="No fixed costs recorded. Add one via the API." diff --git a/src/app/admin/knowledge/page.tsx b/src/app/admin/knowledge/page.tsx index 7c3b89a..70efc89 100644 --- a/src/app/admin/knowledge/page.tsx +++ b/src/app/admin/knowledge/page.tsx @@ -1,4 +1,4 @@ -import { getOrgMemory, InternalMemory } from "@crewcircle/knowledge"; +import { InternalMemory } from "@crewcircle/knowledge"; import { Search, FileText, Database, Zap, Loader2, TrendingUp } from "lucide-react"; export const dynamic = "force-dynamic"; @@ -14,7 +14,8 @@ interface KnowledgeStats { async function getKnowledgeStats(): Promise { try { - const memory = await InternalMemory.create({ provider: "mock" }); + const memory = new InternalMemory({ provider: "mock" }); + await memory.initialize(); const [standards, decisions, projects, retros, techs, adrs] = await Promise.all([ memory.searchStandards("").then(r => r.chunks.length), diff --git a/src/app/admin/projects-table.tsx b/src/app/admin/projects-table.tsx index e7bc067..f66a5af 100644 --- a/src/app/admin/projects-table.tsx +++ b/src/app/admin/projects-table.tsx @@ -2,8 +2,10 @@ import { useRouter } from "next/navigation"; import { DataTable, StatusBadge } from "@crewcircle/admin-ui"; +import type { DataTableColumn } from "@crewcircle/admin-ui"; interface ProjectRow { + [key: string]: unknown; id: string; name: string; status: string; @@ -15,7 +17,7 @@ interface ProjectRow { export function ProjectsTable({ projects }: { projects: ProjectRow[] }) { const router = useRouter(); - const columns = [ + const columns: DataTableColumn[] = [ { key: "name", header: "Project", sortable: true }, { key: "id", header: "ID", sortable: true }, { @@ -54,7 +56,7 @@ export function ProjectsTable({ projects }: { projects: ProjectRow[] }) { ]; return ( - columns={columns} rows={projects} emptyMessage="No projects registered yet. Provision one to get started." diff --git a/src/app/admin/projects/[id]/page.tsx b/src/app/admin/projects/[id]/page.tsx index d1d313d..4dfab4f 100644 --- a/src/app/admin/projects/[id]/page.tsx +++ b/src/app/admin/projects/[id]/page.tsx @@ -2,7 +2,7 @@ import { notFound } from "next/navigation"; import { StatusBadge } from "@crewcircle/admin-ui"; import { readRegistry } from "@/lib/admin/registry"; import { getRepo } from "@/lib/admin/github"; -import { ExternalLink, Github, BarChart3 } from "lucide-react"; +import { ExternalLink, GitFork, BarChart3 } from "lucide-react"; import { DestroyProjectButton } from "./destroy-button"; export const dynamic = "force-dynamic"; @@ -57,7 +57,7 @@ export default async function ProjectDetailPage({ rel="noopener noreferrer" className="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50" > - + GitHub )} diff --git a/src/app/api/admin/costs/fixed/route.ts b/src/app/api/admin/costs/fixed/route.ts index 74c7208..f3f06e0 100644 --- a/src/app/api/admin/costs/fixed/route.ts +++ b/src/app/api/admin/costs/fixed/route.ts @@ -7,7 +7,9 @@ async function checkAuth(): Promise { await requireAdmin(); return null; } catch (e) { - if (e instanceof Response) return e; + if (e instanceof Response) { + return new NextResponse(e.body, { status: e.status, headers: e.headers }); + } return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } } diff --git a/src/app/api/admin/costs/route.ts b/src/app/api/admin/costs/route.ts index 33e3fbe..f186e83 100644 --- a/src/app/api/admin/costs/route.ts +++ b/src/app/api/admin/costs/route.ts @@ -13,7 +13,9 @@ export async function GET(request: NextRequest): Promise { try { await requireAdmin(); } catch (e) { - if (e instanceof Response) return e; + if (e instanceof Response) { + return new NextResponse(e.body, { status: e.status, headers: e.headers }); + } return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/app/api/admin/observability/llm/route.ts b/src/app/api/admin/observability/llm/route.ts index 594cc68..6d55e15 100644 --- a/src/app/api/admin/observability/llm/route.ts +++ b/src/app/api/admin/observability/llm/route.ts @@ -11,7 +11,9 @@ export async function GET(request: NextRequest): Promise { try { await requireAdmin(); } catch (e) { - if (e instanceof Response) return e; + if (e instanceof Response) { + return new NextResponse(e.body, { status: e.status, headers: e.headers }); + } return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/app/api/admin/observability/sentry/route.ts b/src/app/api/admin/observability/sentry/route.ts index 201b4a4..f6432c2 100644 --- a/src/app/api/admin/observability/sentry/route.ts +++ b/src/app/api/admin/observability/sentry/route.ts @@ -11,7 +11,9 @@ export async function GET(): Promise { try { await requireAdmin(); } catch (e) { - if (e instanceof Response) return e; + if (e instanceof Response) { + return new NextResponse(e.body, { status: e.status, headers: e.headers }); + } return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/app/api/admin/observability/uptime/route.ts b/src/app/api/admin/observability/uptime/route.ts index 33ac20f..7ee0b3d 100644 --- a/src/app/api/admin/observability/uptime/route.ts +++ b/src/app/api/admin/observability/uptime/route.ts @@ -11,7 +11,9 @@ export async function GET(): Promise { try { await requireAdmin(); } catch (e) { - if (e instanceof Response) return e; + if (e instanceof Response) { + return new NextResponse(e.body, { status: e.status, headers: e.headers }); + } return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/app/api/admin/projects/route.ts b/src/app/api/admin/projects/route.ts index 7292c96..04dc2f9 100644 --- a/src/app/api/admin/projects/route.ts +++ b/src/app/api/admin/projects/route.ts @@ -13,7 +13,9 @@ export async function GET(): Promise { try { await requireAdmin(); } catch (e) { - if (e instanceof Response) return e; + if (e instanceof Response) { + return new NextResponse(e.body, { status: e.status, headers: e.headers }); + } return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } diff --git a/src/lib/admin/costs.ts b/src/lib/admin/costs.ts index 4e34318..f715879 100644 --- a/src/lib/admin/costs.ts +++ b/src/lib/admin/costs.ts @@ -28,6 +28,7 @@ export interface CostDashboardData { } export interface FixedCostItem { + [key: string]: unknown; id: string; name: string; category: string; diff --git a/src/lib/config/apps.ts b/src/lib/config/apps.ts index 2be76af..e153d71 100644 --- a/src/lib/config/apps.ts +++ b/src/lib/config/apps.ts @@ -5,7 +5,7 @@ export interface AppDef { features: string[]; icon: string; theme: 'orange' | 'blue' | 'green' | 'purple'; - ctaType: 'demo' | 'visit' | 'download'; + ctaType: 'demo' | 'visit' | 'download' | 'coming-soon'; links: { web?: string; appStore?: string; @@ -13,61 +13,96 @@ export interface AppDef { chromeStore?: string; }; description: string; + featured?: boolean; + beta?: boolean; } export const APPS: AppDef[] = [ { - name: 'Crew Roster', - slug: 'crew-roster', - oneLiner: 'Digital timesheet for your crew, sorted.', - features: ['Roster scheduling', 'GPS clock-in/out', 'Award compliance'], - icon: 'Timer', + name: 'TaxFlow', + slug: 'taxflowai', + oneLiner: 'ATO tax research, done.', + features: ['Cited ATO answers', 'ATO letter response drafting', 'Firm knowledge base', 'Document generation'], + icon: 'Calculator', theme: 'orange', ctaType: 'visit', links: { - web: 'https://roster.crewcircle.com', + web: 'https://taxflow.crewcircle.com.au', }, - description: 'Replace paper timesheets with digital clock-ins. GPS-tracked shifts, award compliance rules baked in, and automated payroll export. Your crew signs in from the job site, you get the hours sorted automatically at the end of the week.', + description: + 'AI research assistant for Australian tax professionals. Ask ATO questions in plain English, get cited answers from public rulings and legislation. Upload an ATO letter for a drafted response, generate firm-branded documents, build a private knowledge base.', + featured: true, }, { - name: 'Smart GL', - slug: 'smart-gl', - oneLiner: 'AI does the books for your crew.', - features: ['AI categorisation', 'GST/BAS reports', 'Ledger tracking'], - icon: 'BookOpen', + name: 'LocalMate', + slug: 'localmate', + oneLiner: 'Local business automation for Aussie SMBs.', + features: ['AI review replies', 'Local SEO tracking', 'Competitor monitoring', 'Rebooking follow-ups'], + icon: 'Users', theme: 'blue', ctaType: 'visit', links: { - web: '#', // placeholder — no production URL yet + web: 'https://localmate.crewcircle.com.au', }, - description: 'AI-powered bookkeeping that categorises wages, supplier bills, and expenses automatically. Generates GST/BAS reports ready for your accountant. No cloud dependence, your data stays private.', + description: + 'One dashboard runs five AI jobs: Review Guard drafts replies to Google and Yelp reviews; Rank Report tracks local SEO weekly; Competitor Watch monitors nearby rivals; Rebook follows up lapsed customers by SMS and email; Menu Sync pushes updates from Google Sheets to Google Business Profile and Square.', + featured: true, }, { - name: 'Card Snap', - slug: 'card-snap', - oneLiner: 'Snap cards for your crew.', - features: ['ML Kit OCR', 'Contact save', 'Card history'], - icon: 'Camera', - theme: 'green', - ctaType: 'download', + name: 'CrewRoster', + slug: 'crewroster', + oneLiner: 'Rostering and timesheets for your crew.', + features: ['Shift scheduling', 'GPS clock-in/out', 'Payroll export', 'Award compliance'], + icon: 'Timer', + theme: 'orange', + ctaType: 'visit', links: { - web: '/cardsnap', - appStore: '#ios', - playStore: '#android', + web: 'https://roster.crewcircle.com.au', }, - description: 'Snap a photo of any business card and watch the details land straight in your contacts. Uses on-device ML Kit OCR so nothing leaves your phone. Export to CSV, search history, never lose a contact again.', + description: + 'Digital rostering and timesheets for shift-based crews. Schedule shifts, record GPS clock-in and clock-out, export hours for payroll. Built for Aussie cafes, shops, and tradies. Free for up to 5 employees; paid plans scale per employee.', + featured: false, }, { - name: 'XeroAssist', - slug: 'xero-assist', - oneLiner: 'Xero spanner for your crew.', - features: ['ABN validation', 'BAS tracker', 'ATO rates'], + name: 'SmartGL', + slug: 'smartgl', + oneLiner: 'AI bookkeeping with Australian GST.', + features: ['Bank feed sync', 'AI categorisation', 'Double-entry ledger', 'GST/BAS ready'], + icon: 'BookOpen', + theme: 'blue', + ctaType: 'coming-soon', + links: {}, + description: + 'Connects to Australian bank feeds, auto-categorises transactions, maintains a double-entry ledger with GST/BAS reporting.', + featured: false, + beta: true, + }, + { + name: 'CardSnap', + slug: 'cardsnap', + oneLiner: 'Snap cards, save contacts. Mobile app.', + features: ['On-device OCR', 'Contact save', 'CSV export', 'Search history'], + icon: 'Camera', + theme: 'green', + ctaType: 'coming-soon', + links: {}, + description: + 'Free business-card scanner with on-device OCR. Snap a card, contacts land in your address book. Export CSV, search history.', + featured: false, + beta: true, + }, + { + name: 'AuRate', + slug: 'aurate', + oneLiner: 'ATO admin in one click. Chrome extension.', + features: ['ABN lookup', 'ATO rates', 'BAS reminders', 'Chrome extension'], icon: 'Wrench', theme: 'purple', - ctaType: 'download', - links: { - chromeStore: '#chrome', - }, - description: 'Chrome extension that gives Xero superpowers. Validate ABNs instantly, track BAS deadlines, pull ATO rates without leaving your browser. Built for bookkeepers who live in Xero every day.', + ctaType: 'coming-soon', + links: {}, + description: + 'Free Chrome sidekick for sole traders. Validate ABN, check ATO rates, get BAS reminders without leaving your browser.', + featured: false, + beta: true, }, ]; diff --git a/src/lib/config/content.ts b/src/lib/config/content.ts index 24811b3..f6c6055 100644 --- a/src/lib/config/content.ts +++ b/src/lib/config/content.ts @@ -49,14 +49,13 @@ export const FOOTER_LINKS = { { label: 'Support', href: '/#services' }, ], apps: [ - { label: 'Crew Roster', href: 'https://roster.crewcircle.com' }, - { label: 'Smart GL', href: '#' }, // placeholder — no production URL yet - { label: 'Card Snap', href: '/cardsnap' }, - { label: 'XeroAssist', href: 'https://xero-assist.crewcircle.com' }, + { label: 'TaxFlowAI', href: 'https://taxflow.crewcircle.com.au' }, + { label: 'LocalMate', href: 'https://localmate.crewcircle.com.au' }, + { label: 'CrewRoster', href: 'https://roster.crewcircle.com.au' }, ], legal: [ - { label: 'Privacy Policy', href: '#' }, // placeholder — no legal pages yet - { label: 'Terms of Service', href: '#' }, // placeholder — no legal pages yet - { label: 'Documentation', href: '#' }, // placeholder — no legal pages yet + { label: 'Privacy Policy', href: '/privacy' }, + { label: 'Terms of Service', href: '/terms' }, + { label: 'Documentation', href: '/documentation' }, ], }; diff --git a/tsconfig.json b/tsconfig.json index 4d5552b..e3e3851 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,5 +30,5 @@ ".next/dev/types/**/*.ts", "**/*.mts" ], - "exclude": ["node_modules", "backstage-portal", "crewcircle-website"] + "exclude": ["node_modules", "backstage-portal", "crewcircle-website", "packages"] }