π§± Technology Stack & Codebase Complexity
π§± Technology Stack & Codebase Complexity
π Summary
Midshift is built on a modern, mainstream and deliberately narrow stack: a Next.js 16 / React 19 web application written entirely in TypeScript, hosted on Vercel, with Supabase (managed Postgres, file storage and authentication) as its only data platform and Resend as its only e-mail provider. That narrowness is a strength: a new team needs familiarity with one framework and one backend platform, and hiring for these skills is straightforward. The codebase is medium-sized (about 44,000 lines of application TypeScript, roughly 92,000 lines across everything including tests and tooling) and most functions are simple; complexity is concentrated in a small number of large request handlers and in the in-repository test harness. The main currency concern is that the core framework is pinned to an exact version (Next.js 16.2.0) that carries published critical security advisories, and two declared dependencies are not used at all. Composite complexity: 5 / 10 (moderate).
π― Rating
π‘ βββββ 3 / 5 β Amber. The stack is current, coherent and easy to staff, but the framework is pinned to a vulnerable release with no upgrade mechanism in place, which a buyer must budget to address immediately.
π Findings
1. Languages, frameworks and major libraries
| Component | Version in use | Latest (npm, 2026-10-07) | Status | Evidence |
|---|---|---|---|---|
| Node.js runtime | 22 (.nvmrc; engines: >=22) | 22 LTS | Current (LTS) | [Observed] .nvmrc, package.json |
| TypeScript | 5.9.3 | 7.0.2 | Ageing (one major behind) | [Observed] npm outdated |
| Next.js (App Router, RSC) | 16.2.0, exact pin | 16.4.0 | Current major, but vulnerable minor β see Module 8/9 | [Observed] package.json, npm audit |
| React / React DOM | 19.2.4 (exact pin) | 19.3.0 | Current | [Observed] |
| Tailwind CSS | 4.2.2 | 4.3.3 | Current | [Observed] |
| @supabase/supabase-js | 2.99.3 | 2.117.2 | Current major, ~18 minors behind | [Observed] |
| @supabase/ssr | 0.9.0 | 0.12.7 | Pre-1.0, behind | [Observed] |
| Resend SDK | 6.20.0 (installed) | 6.32.1 | Current | [Observed] |
| mammoth (.docx reading) | 1.12.0 | 1.13.0 | Current | [Observed] |
| xlsx (SheetJS) | 0.18.5 | 0.18.5 on npm (published 2022-03-24) | Unmaintained on npm; vendor now distributes outside npm; known unfixed vulnerabilities | [Observed] npm view xlsx 'time[0.18.5]' |
| ag-psd (Photoshop reading) | 31.0.2 | 31.0.2 | Current | [Observed] |
| fflate (zip) | 0.8.2 | 0.8.3 | Current (patch has a fix) | [Observed] |
| lucide-react (icons) | 0.577.0 | 1.52.0 | Pre-1.0 line, behind a major | [Observed] |
| date-fns | 4.1.0 | 4.4.0 | Declared but not imported anywhere | [Observed] grep across src, scripts, e2e: 0 imports |
| google-auth-library | 10.6.2 | 11.1.0 | Declared but not imported anywhere | [Observed] same grep: 0 imports |
| Vitest | 3.2.7 | 5.0.3 | Two majors behind; critical advisory (dev only) | [Observed] |
| Playwright | 1.62.1 | 1.63.0 | Current | [Observed] |
| ESLint | 9.39.4 | 10.12.0 | One major behind | [Observed] |
2. Runtime, build, infrastructure and hosting
| Concern | Technology | Evidence |
|---|---|---|
| Hosting | Vercel (serverless functions + static assets) | [Observed] vercel.json, README "Architecture" |
| Scheduled jobs | Vercel Cron: /api/cron/purge-review 03:00, /api/cron/overdue 07:00 | [Observed] vercel.json |
| Build | next build (Turbopack-era Next 16) | [Observed] package.json scripts |
| CI | GitHub Actions ci.yml (typecheck, unit tests, scrub, harness contract, lint, build); walk.yml manual | [Observed] .github/workflows/ |
| Infrastructure-as-code | None. Vercel project, Supabase project, storage buckets, env vars and cron secret are configured by hand per deployment (docs/NEW_DEPLOYMENT.md) | [Observed] no Terraform/Pulumi/supabase config.toml present |
| Database migrations | 92 hand-applied SQL files + supabase/baseline.sql for fresh installs; no migration runner | [Observed] supabase/migrations/, CLAUDE.md "Migrations" |
| Deployment model | One Vercel project + one Supabase project per client ("one codebase, never a fork") | [Observed] README "Architecture" |
3. Data stores, queues, caches and third-party services
| Type | Service | Notes |
|---|---|---|
| Relational DB | Supabase Postgres with row-level security | Primary and only data store |
| Object storage | Supabase Storage (campaign-assets, review bucket) | Signed URLs served to the browser |
| Auth | Supabase Auth | Session cookies via @supabase/ssr |
| Resend | Invites, password resets, notifications, feedback | |
| Queue / cache | None | Background work is Vercel Cron only; no queue, no Redis [Observed] |
| AI / LLM | None β the "Ask" feature is rule-based (src/lib/ask-intent.ts) | [Observed] no model SDK in package.json |
4. Measured size (cloc 2.06)
Whole repository (excluding node_modules, .next):
| Language | Files | Blank | Comment | Code |
|---|---|---|---|---|
| TypeScript | 520 | 5,224 | 20,057 | 67,123 |
| JSON | 7 | 0 | 0 | 9,476 |
| SQL | 105 | 742 | 1,931 | 4,674 |
| Markdown | 21 | 608 | 2 | 4,234 |
| CSS | 5 | 164 | 2,427 | 3,402 |
| JavaScript | 14 | 86 | 772 | 1,196 |
| Python | 12 | 132 | 919 | 929 |
| Shell | 14 | 54 | 231 | 523 |
| Other (YAML, text, SVG) | 11 | 158 | 24 | 506 |
| Total | 709 | 7,168 | 26,363 | 92,063 |
src/ only: 465 TypeScript files, 44,380 code lines, 9,934 comment lines (plus CSS). Of src/, the QA harness (src/app/qa) is 38 files / 4,482 code lines and test files are 70. [Observed]
Comment density is unusually high (β22% of TypeScript lines in src are comments), most of it narrative "why" commentary. cloc timed out on four very large spec/script files on the first pass; their lines are therefore slightly under-counted in the whole-repo total. [Observed]
5. Measured complexity (lizard, TypeScript, src/)
2,621 functions; mean cyclomatic complexity (CCN) 2.82.
| CCN band | Functions | Share |
|---|---|---|
| 1β5 | 2,355 | 89.9% |
| 6β10 | 151 | 5.8% |
| 11β20 | 63 | 2.4% |
| 21β50 | 43 | 1.6% |
| > 50 | 9 | 0.3% |
Top 10 most complex functions:
| CCN | NLOC | Location | Function |
|---|---|---|---|
| 138 | 269 | src/app/qa/store.ts:343 | matches (test harness) |
| 109 | 142 | src/lib/for-you.ts:94 | forYouItems ("what needs me" feed) |
| 102 | 135 | src/app/qa/store.ts:1279 | attachJob (test harness) |
| 66 | 154 | src/app/api/campaigns/[id]/ad-units/[unitId]/route.ts:26 | PATCH (ad-unit edit) |
| 64 | 118 | src/app/api/campaigns/[id]/brief/route.ts:27 | PATCH (brief edit) |
| 59 | 141 | src/app/api/review/files/[fileId]/route.ts:43 | PATCH (review decision) |
| 53 | 105 | src/app/api/campaigns/[id]/ad-units/[unitId]/render-push/route.ts:68 | POST |
| 53 | 88 | src/app/qa/api/[...path]/route.ts:226 | handle (test harness) |
| 52 | 91 | src/app/api/campaigns/[id]/jobs/route.ts:31 | POST (create job) |
| 44 | 91 | src/app/api/cron/purge-review/route.ts:29 | GET (file purge) |
Files by summed CCN: src/app/qa/store.ts 1,074; src/app/qa/api/[...path]/route.ts 298; src/lib/schedule-sheet.ts 182; src/lib/for-you.ts 141; src/app/api/campaigns/[id]/plan/route.ts 107. Largest files by lines: src/app/qa/store.ts 3,349; review-view.tsx 1,373; ad-unit-panel.tsx 1,218; src/types/database.ts 1,150; admin-view.tsx 970; copy-deck-parser.ts 828. [Observed]
6. Hotspots
The standard hotspot method (complexity Γ change frequency) cannot be applied: the repository contains a single squashed commit (3789df6, "Midshift source for audit", 2026-10-06), so there is no churn data. [Unavailable β requires the original, unsquashed git history.]
As a proxy (complexity Γ size only, not churn), the candidates a new team would touch most cautiously are: the ad-unit PATCH route, the brief PATCH route, the review-file PATCH route, src/lib/for-you.ts, src/lib/copy-deck-parser.ts (self-described as "the most subtle code in the repo") and src/app/qa/store.ts, which must mirror every API route (see coupling below). [Inferred]
7. Coupling indicators
| Indicator | Measurement | Evidence |
|---|---|---|
| Supabase server/service clients | imported 114 / 98 times | [Observed] grep -rhoE "from '@/lib/..." |
Auth helpers api-auth | imported 67 times | [Observed] |
| Activity logging | imported 53 times | [Observed] |
| Files touching a Supabase client | 154 | [Observed] |
| QA harness mirror | 125 route handlers must have a harness twin (scripts/harness-contract.js enforces it) | [Observed] |
There is no repository/data-access layer between route handlers and Supabase; routes and server components query Supabase directly. [Observed]
8. Composite Complexity Score: 5 / 10
| Dimension | Weight | Score (1β10) | Basis |
|---|---|---|---|
| Size | 25% | 5 | ~44k app LOC, ~92k total β medium for a SaaS product |
| Structural complexity | 30% | 5 | Low mean CCN (2.82) but 52 functions over 20 and 9 over 50, concentrated in route handlers and the harness |
| Stack breadth | 20% | 3 | One framework, one backend platform, one e-mail provider; no queues, caches or microservices |
| Coupling | 25% | 6 | Direct Supabase access in 154 files with no abstraction; harness must mirror every route; fat route handlers mix validation, authorisation and persistence |
| Weighted | 4.85 β 5 |
π§Ύ Evidence
cloc --exclude-dir=node_modules,.next --quiet .andcloc --quiet src(outputs above).lizard src -l typescript --csvβ 2,621 functions; distribution and top-N computed from the CSV.npm outdated(table in Β§1);npm view xlsx 'time[0.18.5]'β2022-03-24T14:23:09.623Z.grep -rn "date-fns\|google-auth-library"over all source, script and test files β no imports.grep -rhoE "from '@/lib/[a-z0-9/-]+'" src | sort | uniq -cfor fan-in.git logβ one commit;git rev-parse --is-shallow-repositoryβfalse(history genuinely squashed, not a shallow clone).- Files read:
package.json,.nvmrc,vercel.json,README.md,.github/workflows/ci.yml. - Mermaid diagram validated with the Mermaid validator (valid: true).
β οΈ Risks
| Risk | Severity | Effort |
|---|---|---|
| Next.js exact-pinned at 16.2.0, a release covered by critical advisories (see Modules 8, 9); exact pins mean no automatic patch uptake | π΄ Critical | S |
xlsx (SheetJS) is frozen on npm since 2022 with unfixed advisories and is used to parse uploaded spreadsheets | π High | SβM |
| No infrastructure-as-code; every client deployment is configured by hand, so environments can drift silently (the docs record migrations found missing in production) | π‘ Medium | M |
| No hotspot analysis possible β squashed history hides where change and defects concentrate | π‘ Medium | β (access request) |
Unused dependencies (date-fns, google-auth-library) enlarge the supply-chain surface for no benefit | βͺ Low | S |
| Several libraries a major behind (TypeScript, Vitest, ESLint, lucide-react, @supabase/ssr pre-1.0) | βͺ Low | M |
β Recommendations
| Recommendation | Priority | Effort |
|---|---|---|
Upgrade Next.js to β₯ 16.4 (and eslint-config-next with it) and switch to caret ranges with a lockfile, so patch releases flow in | P1 (immediate) | S |
Replace xlsx with the vendor's maintained distribution or an alternative (e.g. exceljs), or move parsing fully client-side | P1 | SβM |
Remove date-fns and google-auth-library | P2 | S |
| Introduce infrastructure-as-code (Supabase CLI config + Vercel project config) and an automated migration runner | P2 | M |
| Schedule a dependency-currency pass (Vitest, TypeScript, Supabase SDKs) with Renovate/Dependabot | P2 | M |
| Obtain the unsquashed git history to complete hotspot analysis | P1 (diligence) | β |