105/api/admin/projects has zero callers (~150 lines)
Agent, API & Docs Integrity
mediumOpenTrivial fix
grep '/api/admin/projects' across app code finds only app/api/admin/projects/route.ts. Dashboard loads quotes via /api/admin/submissions (~1387); quotes-admin uses direct Supabase. Orphan admin route with project-message join logic maintained beside the live submission path.
app/api/admin/projects/route.ts
Fix effort: Delete route or wire dashboard projects tab to it instead of submissions-only path.
106/api/internal/webhooks/trigger has no rate limit
API & Operations
mediumOpenTrivial fix
Only app/forklift/page.tsx calls notifyReportCompleted() today; when invoked, /api/internal/webhooks/trigger has no withRateLimit (unlike /api/notifications/trigger). A buggy Forklift loop or future wider adoption could enqueue unbounded webhook delivery jobs.
app/forklift/page.tsx
Fix effort: Wrap with withRateLimit like notifications/trigger.
107/api/public/will-call-items has no callers (~108 lines)
Permissions, Settings & Misleading UI
mediumOpenTrivial fix
Route exists with bridge proxy logic and .env.example still lists it as a live proxy, but grep finds no fetch('/api/public/will-call-items') in app code. /sales loads line items via /api/public/sales-relay (SalesClient.tsx ~878+). Comment in sales still references will-call-items 'when missing' but code path never calls it — stale docs + dead route.
Fix effort: Delete route and remove from .env.example / protected-paths, or wire SalesClient fallback.
108/noe and /adrian kiosks are absent from operational navigation
Agent, API & Docs Integrity
mediumOpenTrivial fix
Public will-calls (/noe) and routes/vendor-pickups (/adrian) kiosks are documented in /docs and ArchitectureDiagram but grep finds no entries in lib/greco/greco-tools.ts GRECO_TOOLS cards. Same discoverability class as tracked /port gap — floor staff must know URLs; no dashboard or tools-grid entry for two heavily documented public kiosks.
lib/greco/greco-tools.ts
Fix effort: Add GRECO_TOOLS cards or Workspace kiosk folder linking /noe and /adrian.
109/quotes-admin analytics cards show company metrics as platform totals
Controls, Admin & Collaboration
mediumOpenTrivial fix
loadStats (~162–166) counts profiles and companies via user-scoped Supabase client — RLS returns one company. Cards labeled like platform-wide signups/client IDs are actually company-scoped counts on a page styled as global admin.
Fix effort: Relabel stat cards or fetch via GodView service-role endpoint.
110132_create_thruput_tables.sql is an empty stub
Backend & Database
mediumOpenTrivial fix
supabase/migrations/132_create_thruput_tables.sql is a single-line comment — no CREATE TABLE. Prefix 132 collides with 132_add_item_alert_stages.sql (alphabetical apply order picks _add_ before _create_). app/thruput/page.tsx + lib/supabase/tool-reports.ts saveThruputWeek/Day (~2075–2165) target thruput_days/thruput_weeks. Fresh migration runs never create those tables; throughput board edits fail or rely on hand-run SQL outside repo history.
supabase/migrations/132_create_thruput_tables.sqlapp/thruput/page.tsxlib/supabase/tool-reports.ts
Fix effort: Delete stub or fill with real CREATE TABLE; resolve duplicate 132 prefix.
111Activity-log cleanup archives at most 10,000 rows per day
Agent, API & Docs Integrity
mediumOpenTrivial fix
app/api/cron/activity-log-cleanup/route.ts ~42 uses .limit(10_000) per daily run. If more than 10k rows exceed the 365-day cutoff, remainder waits for subsequent days with no overflow logging in the JSON response. High-volume warehouse tenants can leave a growing tail of expired activity.
app/api/cron/activity-log-cleanup/route.ts
Fix effort: Loop until batch empty or log remaining count in cron JSON response.
112ai-usage-rollup cron ignores upsert failures
API & Operations
mediumOpenTrivial fix
app/api/cron/ai-usage-rollup/route.ts monthly rollup loop awaits ai_usage_monthly_rollups upsert without checking error. Returns { success: true, companiesRolledUp: N } even when individual upserts fail — GodView AI usage dashboards can drift from raw ai_usage_logs.
app/api/cron/ai-usage-rollup/route.ts
Fix effort: Check upsert error per company; increment failed count in response.
113ALL_MIGRATIONS_COMBINED.sql is misnamed — contains only migrations 001–011
Repository Structure Audit (Pass 23)
mediumOpenTrivial fix
File header says 'COMPLETE MIGRATION FILE' and 'combines all 11 migrations' (~403 lines) while 139+ numbered migrations exist with 13 duplicate prefixes (027, 028, … 132). Filename implies one-shot fresh install; reality is a historical artifact from early multi-tenancy bootstrap. Rename to 001-011_legacy_combined.sql or delete to avoid false confidence on new environments.
Fix effort: Rename to 001-011_legacy_combined.sql or delete; add warning header if kept.
114Architecture diagram claims greco-digest emails stakeholders
Agent, API & Docs Integrity
mediumOpenTrivial fix
app/docs/ArchitectureDiagram.tsx ~1117: 'Nightly /api/cron/greco-digest rolls up the day and emails stakeholders.' app/api/cron/greco-digest/route.ts only calls addMemoryEntry/searchMemory (~161–237) — grep finds no email import or send in that file. Docs overstate nightly email delivery; operators configuring around that flow are misled.
app/docs/ArchitectureDiagram.tsxapp/api/cron/greco-digest/route.ts
Fix effort: Fix ArchitectureDiagram copy or add email step to greco-digest cron.
115Architecture diagram misdocuments Return Diffs — undermines integration truth
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenTrivial fix
app/docs/ArchitectureDiagram.tsx (~1102–1106) claims /returns-intel ingests weekly BFC truck-errors and feeds return_diffs_snapshots. Actual flow: BFC xlsx drag-drop in Window Return Diffs modal + /return-diffs ODBC → return_diffs_snapshots (EntreeClient.tsx ~9890+). /returns-intel writes returns_intel_reports only. Wrong docs slow enterprise merge work — new integrators will wire the wrong tables.
app/docs/ArchitectureDiagram.tsx
Fix effort: Fix ArchitectureDiagram.tsx Return Diffs flow arrow — point at Window modal + return_diffs_snapshots, not /returns-intel.
116Build page truncates generation errors on mobile
UI, Flow & Density
mediumOpenTrivial fix
app/build/page.tsx error banner uses max-w-[100px] truncate on mobile (~1695–1696) while desktop gets max-w-xs. Failed generation messages (API errors, validation text) are clipped to ~100px — operators on phones see 'Gener…' instead of the actionable error string.
app/build/page.tsx
Fix effort: Remove max-w-[100px] on mobile error banner; use multi-line text or expandable error panel.
117Calendar event form initializes date in UTC
Controls, Admin & Collaboration
mediumOpenTrivial fixHigh blast radius
EventForm in app/calendar/page.tsx (~128) sets date via defaultDate.toISOString().split('T')[0]. Evening local times can show tomorrow in the date input before the operator adjusts — distinct from warehouse report_date UTC issues but same operator confusion on a collaboration surface.
app/calendar/page.tsx
Fix risk: Date boundary changes reorder stats, leaderboards, and filters keyed on existing date columns.
Fix effort: Use warehouse-local date helper in EventForm default date state.
118Controls shows 'admin-only' to logged-out visitors
Auth & Session
mediumOpenTrivial fix
app/controls/ControlsClient.tsx sets role='viewer' when getUser() returns null (lines 87–88) and renders the 'Controls are admin-only' card — no /login redirect. Logged-out visitors see a misleading access-denied screen instead of the login page.
app/controls/ControlsClient.tsx
Fix effort: router.push('/login') when !user instead of setRole('viewer') in ControlsClient.tsx.
119Custom-tool AI routes lack maxDuration for multi-phase generation
Build, IDE & Custom Tools
mediumOpenTrivial fix
app/api/custom-tools/generate/route.ts and generate-from-file/route.ts run multi-phase gateway calls (timeoutMs up to ~55s per phase, up to 4 phases) but export no maxDuration. On Vercel, platform defaults can kill mid-pipeline → flaky 504s from Build and /vibe file/prompt modes.
app/api/custom-tools/generate/route.ts
Fix effort: export const maxDuration = 90 on generate + generate-from-file routes.
120Daily Items skips notifyReportGenerated on PDF export
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenTrivial fix
app/items/page.tsx calls logReportGeneration('Daily Items', …) (~349) with blob size 0 and never notifyReportGenerated. Print Schedule (app/schedule/page.tsx ~244–252) does both. Per-tool email recipients and webhook subscribers never fire for Daily Items runs despite being a core receiving document.
app/items/page.tsxapp/schedule/page.tsx
Fix effort: Call notifyReportGenerated after logReportGeneration in app/items/page.tsx.
121Dashboard Build hub omits the /build custom-tool builder
UI, Flow & Density
mediumOpenTrivial fix
activeTab === 'build' (~2162–2193) only links to /vibe and /ide. The standalone /build page (file/prompt custom-tool generation, refine, publish) is the third build surface but has no card in the Build hub — users who land on the dashboard Build tab never discover the main tool builder unless they know the URL.
Fix effort: Add third Build hub card linking to /build with description distinct from Vibe and IDE.
122Dashboard navigating tabs lost their ↗ marks (regression)
UI, Flow & Density
mediumOpenTrivial fix
Audit tab 'Tab bar mixes tabs with links…' is marked fixed with ↗ on Account, AI, Docs, Shanetrée, DockSheet and Support. app/dashboard/page.tsx tab strip (~1768–1910) has zero ↗ characters — in-place tabs and <Link> navigations look identical again. Users can't tell Overview/Tools/Workspace/Reports (stay on dashboard) from Account/Chat/Docs/Entree (leave page) before clicking.
app/dashboard/page.tsx
Fix effort: Re-add ↗ suffix to dashboard tab-bar <Link> entries (Account, Chat, Docs, Entree, DockSheet, etc.) per audit fix.
123DockSheet custom-appointments panel swallows load failures
Agent, API & Docs Integrity
mediumOpenTrivial fix
app/tools/greco/CindyVueClient.tsx ~334–338: fetch('/api/greco/custom-appointments') uses .catch(() => {}) with no error state. Failed load renders empty appointment list indistinguishable from 'no custom appointments' — operators may think the feature is unused when the API failed.
app/tools/greco/CindyVueClient.tsx
Fix effort: Surface fetch error inline in CindyVueClient custom-appointments section.
124DockSheet dockvoo daily-stat upsert swallows errors
Greco Warehouse Operations
mediumOpenTrivial fix
CindyVueClient.tsx (~855) calls upsertDailyStat(dateStr, 'dockvoo', statPayload).catch(() => {}) with no toast or retry. Failed greco_daily_stats writes leave Daily Stats and greco-digest blind to dock metrics while the DockSheet UI still shows live counts — silent divergence between floor view and analytics rollups.
Fix effort: Surface upsertDailyStat failure in CindyVue sync banner instead of .catch(() => {}).
125Document notification route counts failed Resend sends as delivered
API & Operations
mediumOpenTrivial fix
app/api/notifications/document/route.ts increments emailsSent after fetch('https://api.resend.com/emails') without checking response.ok. A 401/422 from Resend still returns { success: true, emailsSent: N } to the caller.
app/api/notifications/document/route.ts
Fix effort: if (!response.ok) skip emailsSent++ in notifications/document/route.ts.
126Driver Scan silently swallows Supabase persistence failures
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenTrivial fix
app/driver-scan/DriverScanClient.tsx — saveDriverScanReport(...).catch(() => {}) and upsertDailyStat(...).catch(() => {}) (~231–232). Cloud save/My Reports paths surface warnings (~212–214), but driver_scan_reports and greco_daily_stats fail with zero UI feedback — analytics blind to failed saves.
app/driver-scan/DriverScanClient.tsx
Fix effort: Surface toast on saveDriverScanReport/upsertDailyStat catch — match cloud-save warning pattern.
127Email service logs Resend API key prefix to server console
Security & Trust Boundaries
mediumOpenTrivial fix
lib/email/service.ts sendWithResend() logs [EMAIL] RESEND_API_KEY prefix: with the first six characters on every send attempt. Server logs (Vercel drains) become a partial secret oracle — separate from the Sentry replay issue.
lib/email/service.ts
Fix effort: Delete the RESEND_API_KEY prefix console.log in lib/email/service.ts line 172.
128Four member-permissions wrapper exports are never called
Dead Code Hunt (Pass 22)
mediumOpenTrivial fixHigh blast radius
lib/supabase/member-permissions.ts exports canAccessToolAnalytics() (~335), canAccessToolAI() (~363), canEditEmailSettings() (~391) and canEditAIFilters() (~419) with zero external callers. Tool pages use getToolPagePermissions() instead; canEdit* fields on getMyFullPermissions() are already tracked as unread — these standalone async wrappers are a second dead layer on top.
lib/supabase/member-permissions.ts
Fix risk: Wiring latent toggles suddenly enables or blocks behavior users assumed was active.
Fix effort: Remove canAccessToolAnalytics, canAccessToolAI, canEditEmailSettings, canEditAIFilters from member-permissions.ts; callers already use getToolPagePermissions / getMyFullPermissions.
129Geronimo, Licenses and Negative Slots never write greco_daily_stats
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenTrivial fix
app/geronimo/GeronimoClient.tsx logs + notifyReportGenerated (~673–674) but never upsertDailyStat. app/licenses/page.tsx (~440–447) and app/negative-slots/page.tsx (~461–468) same pattern. Empty Slots gap is tracked separately. All three are absent from DailyStatsClient TOOL_META (~62–80) — digest cron and Daily Stats grid stay blind.
app/geronimo/GeronimoClient.tsxapp/licenses/page.tsxapp/negative-slots/page.tsx
Fix effort: Add upsertDailyStat on save + TOOL_META entries for all three — mirror driver_scan pattern.
130GET /api/greco/po-doc skips Greco company gate when env is unset
Agent, Collaboration & Platform APIs
mediumOpenTrivial fix
app/api/greco/po-doc/route.ts (~64–73) wraps the GRECO_COMPANY_ID check in if (GRECO_COMPANY_ID). When NEXT_PUBLIC_GRECO_COMPANY_ID is empty (misconfigured deploy, staging), any authenticated user can enqueue bridge/ODBC po-doc relay jobs — ODBC cost and Entrée load without tenant pin.
app/api/greco/po-doc/route.ts
Fix effort: Fail closed when GRECO_COMPANY_ID unset — return 503 instead of skipping gate.
131getTruckErrorsReportsRaw is orphaned after Return Diffs decoupled from /truck-errors
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenTrivial fix
lib/supabase/tool-reports.ts (~750–808) exports getTruckErrorsReportsRaw() to feed Return Diffs from truck_errors_reports JSONB. EntreeClient Return Diffs comment (~9899–9903) says BFC xlsx drag-drop replaced that path so operators can diff any file without uploading through /truck-errors first. Grep shows zero callers besides the definition — dead integration code that signals the truck-errors → Return Diffs pipeline was abandoned mid-merge.
lib/supabase/tool-reports.ts
Fix effort: Delete dead helper or rewire as optional Return Diffs BFC source — document chosen path in ArchitectureDiagram.
132GodView admin list queries accept unbounded limit parameter
API & Operations
mediumOpenTrivial fixMay affect neighbors
app/api/admin/godview/route.ts parseInt(searchParams.get('limit') || '100') has no max cap on users, activity, reports, email_logs, etc. One authenticated admin session can pull multi-megabyte cross-tenant payloads — separate from the tracked internal /api/anomalies limit gap.
app/api/admin/godview/route.ts
Fix risk: Adding caps breaks export or load-all flows that depend on fetching entire tables.
Fix effort: Clamp limit to e.g. 500 in godview/route.ts parseInt.
133GodView list endpoints return total without requesting a count
Controls, Admin & Collaboration
mediumOpenTrivial fix
getUsers, getProjects and sibling handlers in app/api/admin/godview/route.ts (~334–356, ~382) destructure count from .select(...) without { count: 'exact' }. total is always null/undefined — GodView pagination UI is broken across users, projects, chats and logs tabs. Separate from tracked unbounded limit cap — this is missing count plumbing even at normal limits.
app/api/admin/godview/route.ts
Fix effort: Add { count: 'exact' } to godview list .select() calls.
134Health check probes GOOGLE_AI_API_KEY, not GOOGLE_API_KEY
API & Operations
mediumOpenTrivial fix
app/api/health/route.ts checkAIService (~219) gates on process.env.GOOGLE_AI_API_KEY. Grep GOOGLE_API_KEY across app/api shows ai-summary, ai-analyze, routing/ai-build and agent/chat all read GOOGLE_API_KEY per .env.example. Production with only GOOGLE_API_KEY set: Gemini works, ?detailed=true health reports AI degraded — false alarm in ops runbooks.
app/api/health/route.tsapp/api
Fix effort: One env var name fix in app/api/health/route.ts line 219.
135HistoryPanel never refreshes after first open
Data Flows & Wiring
mediumOpenTrivial fix
app/components/HistoryPanel.tsx loads only when open && !loaded; loaded is set true on first fetch and never reset on close/re-open (comment says it refreshes but code does not). Operators who close the slide-over, generate a report, and reopen see stale data until full page refresh.
app/components/HistoryPanel.tsx
Fix effort: Reset loaded=false on panel close, or reload whenever open transitions false→true.
136Internal routes accept unbounded limit params
API & Operations
mediumOpenTrivial fixMay affect neighbors
app/api/anomalies/route.ts:72 parses parseInt(searchParams.get('limit') || '50') with no Math.min cap and passes limit to getRecentAnomalies RPC. Sibling /api/v1/anomalies/route.ts:35–36 clamps days to 1–90 and limit to 200. app/api/jobs/route.ts:121 has same hole — limit flows into .range() on JSONB job payloads. Authenticated session holders can request ?limit=999999.
app/api/anomalies/route.tsapp/api/jobs/route.ts
Fix risk: Capping limit may break internal dashboards or GodView scripts using ?limit=5000 today.
Fix effort: Copy clamp logic from v1 twins into anomalies + jobs routes (~5 lines each).
137Internal routes accept unbounded limit params their v1 twins clamp
API Consistency
mediumOpenTrivial fixMay affect neighbors
/api/anomalies GET parses ?limit= and ?days= with no cap and passes them straight to the get_recent_anomalies RPC, while /api/v1/anomalies clamps days to 1-90 and limit to 200. /api/jobs GET has the same hole — limit flows uncapped into .range(), payload JSON blobs and all. Anyone authenticated can request limit=999999.
Fix risk: Duplicate — anomalies/jobs large pulls may be intentional for exports.
Fix effort: Same limit caps.
138lib/agent/policy-verification.ts is an entire unused module
Dead Code Hunt (Pass 22)
mediumOpenTrivial fix
Exports verifyPolicyPrecedence() and verifyEventCompleteness() (~62 lines) for agent policy precedence checks. Grep across the repo finds zero imports of policy-verification or either function — lib/agent/policy.ts and orchestrator resolve effective policy without these test helpers. Built for policy QA that was never wired into CI or runtime.
lib/agent/policy.ts
Fix effort: Delete lib/agent/policy-verification.ts or wire into agent policy tests if QA value remains.
139lib/routing/geocoder.ts is an entire unused module
Dead Code Hunt (Pass 22)
mediumOpenTrivial fix
Exports geocodeAddress(), batchGeocode(), getOSRMRoute() and unit helpers (~109 lines). app/tools/greco/routing/RoutingClient.tsx calls fetch('/api/routing/geocode') and inline OSRM instead. Grep finds zero imports of @/lib/routing/geocoder — duplicate geocoding logic maintained in a dead client module beside the live API route.
app/tools/greco/routing/RoutingClient.tsxlib/routing/geocoder
Fix effort: Delete lib/routing/geocoder.ts; RoutingClient already uses /api/routing/geocode.
140lib/supabase/api-keys.ts is an orphan client module
Permissions, Settings & Misleading UI
mediumOpenTrivial fix
The ~377-line module documents CRUD for API keys 'used by both API routes and settings UI,' but grep finds zero imports of lib/supabase/api-keys anywhere. Developer page uses fetch('/api/settings/api-keys') over HTTP instead. Entire typed client layer is dead weight beside the live route handlers.
lib/supabase/api-keys
Fix effort: Delete ~377-line module or wire developer page through it instead of raw fetch.
141Members 'Ask AI' writes aiChatPrefill that nothing reads
Data Flows & Wiring
mediumOpenTrivial fix
app/members/page.tsx line 80 sessionStorage.setItem('aiChatPrefill', question) before navigating to /chat, but grep across the entire app finds zero readers for that key. The button appears to wire a prefill flow that was never finished on the chat page.
app/members/page.tsx
Fix effort: Read aiChatPrefill in app/chat/page.tsx on mount (or remove the dead sessionStorage write).
142Monitoring activeToday queries non-existent team_activity table
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenTrivial fix
app/api/monitoring/metrics/route.ts getUserMetrics (~259–268) selects from team_activity, but migrations only define team_activity_log (016_create_team_activity_log.sql). Query silently returns empty — activeToday always reads 0 in GodView/monitoring dashboards even when warehouse staff are active.
app/api/monitoring/metrics/route.ts
Fix effort: Change .from('team_activity') to team_activity_log with correct timestamp column in monitoring/metrics/route.ts.
143Per-tool recipient helper exports are never called
Dead Code & Orphan Features
mediumOpenTrivial fix
lib/supabase/report-email-recipients.ts exports getRecipientUserIds() and getAllRecipientEmails() (~50 lines) with zero imports. Dashboard loads recipients via getEmailRecipients for the settings UI, and v1 analyze uses getAllRecipientsForCompany — but the two helper wrappers built to drive notification sends were never wired. Reinforces that per-tool email config is decorative on the UI path.
lib/supabase/report-email-recipients.ts
Fix effort: Delete getRecipientUserIds/getAllRecipientEmails or wire into notifyReportGenerated path.
144Picker pipeline logs report metadata to the browser console
Code Quality & Hygiene
mediumOpenTrivial fix
lib/supabase/tool-reports.ts savePickerReport and getPickerReports console.log company_id, user_id, report data payloads and auth email. Visible on shared warehouse machines — debug logging that should have been stripped.
lib/supabase/tool-reports.ts
Fix effort: Delete console.log lines in lib/supabase/tool-reports.ts.
145POST /api/custom-tools has no role gate — viewers can create tools via API
Agent, Collaboration & Platform APIs
mediumOpenTrivial fix
app/api/custom-tools/route.ts POST (~92–130) authenticates any company user and rate-limits, but never checks profile.role. UI may restrict the Build page to editors, but the API accepts up to 500KB code payloads from viewers. Enterprise SaaS should mirror UI role gates on mutating routes.
app/api/custom-tools/route.ts
Fix effort: Reject POST when profile.role === 'viewer' — match Build page gate.
146Projects project-chat send failures are silent
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenTrivial fix
app/projects/page.tsx sendProjectChatMessage() (~61–100) catches errors with console.error only — no toast, inline error, or disabled-state feedback. Users believe a message sent when the Supabase insert failed. Missing session gate on mount is tracked separately.
app/projects/page.tsx
Fix effort: Toast or inline error in sendProjectChatMessage catch — app/projects/page.tsx.
147Public item-alert subscribe sends email with no rate limit
Rate Limits & Abuse Surface
mediumOpenTrivial fix
/api/public/item-alert-subscribe is an unauthenticated POST that inserts a subscription row and fires a confirmation email through Resend on every call — with no withRateLimit. Repeated { item, email } posts mean unbounded outbound email cost and DB writes from anyone on the internet.
Fix effort: Add withRateLimit to item-alert-subscribe route.
148Public kiosk routes are absent from GRECO_TOOLS
Greco Warehouse Operations
mediumOpenTrivial fix
lib/greco/greco-tools.ts GRECO_TOOLS ends at /windowadmin — no entries for /inbound, /timer, /live/greco, /sales or /window (public booking). These routes are documented in ArchitectureDiagram and wired to greco session APIs, but founders cannot hide them via the /controls tool matrix and operators won't find them from the Tools tab without memorizing URLs (same discoverability class as tracked /port, /noe, /adrian gaps).
lib/greco/greco-tools.ts
Fix effort: Add optional kiosk folder entries for /inbound, /timer, /live/greco, /sales, /window in greco-tools.
149Public tool info endpoint has no rate limiting
Build, IDE & Custom Tools
mediumOpenTrivial fix
app/api/public-tools/info/route.ts returns full tool name + code with zero withRateLimit, while sibling /api/public-tools/data is rate-limited. Enables share_id enumeration and scraping of generated tool source at higher volume.
app/api/public-tools/info/route.ts
Fix effort: Add withRateLimit to public-tools/info like data route.
150Relay mode hides the DockSheet tile in Shanetrée
Greco Warehouse Operations
mediumOpenTrivial fix
RELAY_HIDDEN_MODALS in EntreeClient.tsx (~446–447) includes 'dockVoo' alongside print, itemSearch and guide. Relay/tablet Shanetrée home (~9162, ~29421) filters modal tiles with !(relayMode && RELAY_HIDDEN_MODALS.has(key)) — dock-floor relay devices cannot open DockSheet from the Entree launcher even though /cindyvue and dashboard DockSheet links still work.
Fix effort: Remove dockVoo from RELAY_HIDDEN_MODALS or add explicit DockSheet shortcut on relay home.
151Returns-family card labels don't match their routes
UI, Colors & Aesthetics
mediumOpenTrivial fixHigh blast radius
lib/greco/greco-tools.ts GRECO_TOOLS: line 65 name 'Returns' → /truck-errors (truck error upload), line 67 'Returns+' → /returns (ledger), line 74 'Weekly Returns' → /returns-intel. greco_card_permissions keys by route — renames break denials. Labels mislead new users; safe fix is display-name only ('Truck Errors', 'Returns Ledger', 'Returns Intel').
lib/greco/greco-tools.ts
Fix risk: greco_card_permissions keys by route path — renaming display labels is safe but changing routes breaks denials and saved permissions JSON.
Fix effort: Rename card labels in greco-tools.ts only — routes/permission keys stay.
152Returns-family names don't match their routes
Naming & Labels
mediumOpenTrivial fixHigh blast radius
lib/greco/greco-tools.ts GRECO_TOOLS maps three confusing pairs: line 65 name 'Returns' → route /truck-errors (truck error returns upload), line 67 'Returns+' → /returns (customer returns ledger), line 74 'Weekly Returns' → /returns-intel (weekly intel). greco_card_permissions and member_permissions.tool_access key by route string — renaming routes would break saved denials. Card labels could say 'Truck Errors', 'Returns Ledger', 'Returns Intel' without touching permission keys.
lib/greco/greco-tools.ts
Fix risk: Same route-keyed permissions — '/returns' vs 'Returns+' label confusion is safer than route renames.
Fix effort: Same label rename.
153Routing load and export failures often fail silently
Shanetrée, Routing & Driver Comms
mediumOpenTrivial fix
loadRouteList ignores non-OK fetch responses (RoutingClient.tsx ~891–894). loadRoute returns false with no banner (~901–928). Export swallows saveOutputFile, logReportGeneration and notifyReportGenerated via .catch(() => {}) (~1439–1441). Operators can believe a route saved or exported when the API failed — empty load menu looks like 'no saved routes' not an error.
Fix effort: Surface res.ok failures and export .catch errors inline in RoutingClient.
154routing/ai-build missing maxDuration for 90s AI call
API & Operations
mediumOpenTrivial fix
app/api/routing/ai-build/route.ts calls gateway with timeoutMs: 90_000 (~445) but exports no export const maxDuration. Sibling app/api/port/ai-summary/route.ts:36 sets maxDuration = 90. Vercel platform default function timeout can kill mid-cluster generation → intermittent 504 on route planner AI builds.
app/api/routing/ai-build/route.tsapp/api/port/ai-summary/route.ts
Fix effort: export const maxDuration = 90 — one line, copy from port/ai-summary.
155routing/ai-build waits 90s for AI but never declares maxDuration
API Consistency
mediumOpenTrivial fix
The route calls the gateway with timeoutMs: 90_000 yet exports no maxDuration — on Vercel the platform default can kill the function mid-generation, surfacing as flaky 504s. Sibling AI routes (port/ai-summary at 90, driver-chat/ai at 60) declare it; this one just forgot.
Fix effort: Same maxDuration export.
156Sentry session replay records unmasked page text
Security & Trust Boundaries
mediumOpenTrivial fix
sentry.client.config.ts sets maskAllText: false and blockAllMedia: false with replaysSessionSampleRate 0.1 and replaysOnErrorSampleRate 1.0. Warehouse metrics, customer names and tool output can land readable in Sentry. Masking should be on by default with targeted unmasking.
Fix effort: Flip maskAllText: true in sentry.client.config.ts; unmask specific selectors if needed.
157Server-side request errors may never reach Sentry
API & Operations
mediumOpenTrivial fix
instrumentation.ts exports only register() (~8–16) importing sentry.server.config and sentry.edge.config — no export async function onRequestError. @sentry/nextjs v10 App Router docs require onRequestError to capture route-handler exceptions. Client path has app/global-error.tsx; grep onRequestError across repo returns zero matches. API route throws likely never become Sentry events unless manually captured.
app/global-error.tsx
Fix effort: Export onRequestError from instrumentation.ts per @sentry/nextjs v10 docs.
158Shanetrée autopull hardcodes a fallback user UUID
Backend & Database
mediumOpenTrivial fix
app/api/internal/shanetree-autopull/route.ts attributes snapshots and pull-log rows to SHANETREE_AUTOPULL_USER_ID, falling back to hardcoded f528c8d6-f7db-4d34-a656-680a3d3741de. On a fresh deploy without that env var, activity ledger, pull log and snapshots are stamped with one fixed user regardless of who operates the bridge.
app/api/internal/shanetree-autopull/route.ts
Fix effort: Require SHANETREE_AUTOPULL_USER_ID env var (fail if missing) instead of hardcoded fallback UUID.
159Shrink Meeting and Counting skip greco_daily_stats integration
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenTrivial fix
app/shrink-meeting/page.tsx logs + notifyReportGenerated (~370) but never upsertDailyStat. app/counting/CountingClient.tsx logs via logTeamActivity (~531–542) only — no upsertDailyStat, logReportGeneration, or notifyReportGenerated. Meat-count ledger entries appear in /history but Daily Stats grid and greco-digest stay blind.
app/shrink-meeting/page.tsxapp/counting/CountingClient.tsx
Fix effort: Add upsertDailyStat + TOOL_META keys for shrink_meeting and counting on save.
160StockVoo disables live refresh in portMode without stale-data warning
Email, v1 API & Entrée Panel Polish
mediumOpenTrivial fix
app/entree/StockVooPanel.tsx (~733) sets disabled={loading || portMode} on Refresh. /port relay tablet users cannot re-pull live inventory ODBC; UI does not explain they are viewing a frozen snapshot — operators may assume stock counts are current.
app/entree/StockVooPanel.tsx
Fix effort: Show 'snapshot only on /port' banner when portMode disables Refresh — StockVooPanel.tsx.
161supabase/migrations/README.md is stale — documents 11 migrations, repo has 150
Repository Structure Audit (Pass 23)
mediumOpenTrivial fix
supabase/migrations/README.md 'Migration Files' section ends at 011_create_truck_errors_reports_table.sql with hand-run SQL Editor instructions. git ls-files counts 150 migration SQL files including 136_create_account_geocodes.sql. Fresh DBA following README applies the wrong subset. Professional fix: auto-generate migration index from filenames or delete README body and point to Supabase CLI only.
supabase/migrations/README.md
Fix effort: Rewrite README to point at Supabase CLI only or auto-generate file list from migrations/.
162Team-chat ?dm= deep link fails silently when room creation errors
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenTrivial fix
app/team-chat/page.tsx (~162–172) calls createChatRoom('', [dmUserId], 'direct') when no existing DM exists. On !result.success there is no toast, error state, or fallback — user stays on room list with no explanation. Broken DM links from mentions or dashboard look like a no-op.
app/team-chat/page.tsx
Fix effort: Show toast/setError when createChatRoom returns !success in team-chat/page.tsx.
163Team-chat create-room failure is silent
Controls, Admin & Collaboration
mediumOpenTrivial fix
handleCreateChat in app/team-chat/page.tsx (~262–283): createChatRoom failure has no else branch — no toast, no error state. Modal stays open with no feedback when room creation fails (RLS denial, duplicate direct message, network blip).
app/team-chat/page.tsx
Fix effort: Show error toast in handleCreateChat else branch when result.success is false.
164Team-chat never triggers chat email notifications
Data Flows & Wiring
mediumOpenTrivial fix
notifyChatMessage() exists in lib/notifications/trigger.ts but has zero callers outside its definition. team-chat handleSendMessage only inserts via sendChatRoomMessage — users who enable chat_notifications in account settings never get emails from team-chat activity.
lib/notifications/trigger.ts
Fix effort: Call notifyChatMessage() after successful sendChatRoomMessage in team-chat/page.tsx.
165The health check tests an env var that nothing else uses
API Consistency
mediumOpenTrivial fix
/api/health's AI probe checks GOOGLE_AI_API_KEY, but every actual AI route reads GOOGLE_API_KEY (which is what .env.example documents). Production can have Gemini fully working while health reports AI as degraded — false alarms baked into the runbook.
Fix effort: Same env var rename.
166Thruput load failures render an empty board with no error state
Greco Warehouse Operations
mediumOpenTrivial fix
Initial week/day hydration in app/thruput/page.tsx (~171–207) catches errors with console.error only — no setError banner. A missing thruput_days table or RLS denial looks identical to 'no data yet this week,' so night-shift leads may rebuild staffing from scratch while the real rows failed to load.
app/thruput/page.tsx
Fix effort: setError banner in thruput/page.tsx catch blocks when getThruputWeeks/Days fails.
167Thruput ships hardcoded real employee names as defaults
Greco Warehouse Operations
mediumOpenTrivial fix
DEFAULT_ACTIVE in app/thruput/page.tsx (~114–129) seeds early500, loaders, runners, forklift areas, management and sick/off lists with real Greco staff names (e.g. 'Chilchoa, Moises', 'Acabal, Ramon', 'Cisneros, Rodolfo'). New weeks/days inherit that roster until manually cleared — a privacy and onboarding hazard if the page is ever shared outside the warehouse or reused for demos.
app/thruput/page.tsx
Fix effort: Replace DEFAULT_ACTIVE with empty placeholders or load from last saved week.
168Transfers and Cycle Count swallow upsertDailyStat failures
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenTrivial fix
app/transfers/TransfersClient.tsx (~128, 136) and app/cyclecount/CycleCountClient.tsx (~128–129) call upsertDailyStat(...).catch(() => {}) and notifyReportGenerated(...).catch(() => {}) with no UI feedback. Driver Scan silent-save pattern is tracked — Transfers/Cycle Count have the same analytics divergence risk.
app/transfers/TransfersClient.tsxapp/cyclecount/CycleCountClient.tsx
Fix effort: Toast on catch instead of empty .catch(() => {}) — match driver-scan fix.
169v1 /tools/generate always publishes tools (is_public: true)
Build, IDE & Custom Tools
mediumOpenTrivial fix
app/api/v1/tools/generate/route.ts (~103) hardcodes is_public:true on every API-generated tool, minting a public /t/{share_id} surface regardless of builder intent. Pairs with tracked 'no in-app public link' discoverability gap — API path auto-publishes.
app/api/v1/tools/generate/route.ts
Fix effort: Accept is_public param defaulting false; document public share opt-in in OpenAPI.
170v1 batch API blocks AI chat routes that exist as first-class endpoints
Build, IDE & Custom Tools
mediumOpenTrivial fix
ALLOWED_PATH_PREFIXES in app/api/v1/batch/route.ts (~38–51) includes /api/v1/agent but not /api/v1/ai/chat or /api/v1/ai/chat/stream despite both being live in openapi.json. Batch clients get 400 for AI chat operations. Calendar batch gap is tracked separately.
app/api/v1/batch/route.ts
Fix effort: Add /api/v1/ai/chat and /api/v1/ai/chat/stream to ALLOWED_PATH_PREFIXES.
171v1 batch API blocks calendar routes that exist as first-class endpoints
API & Operations
mediumOpenTrivial fix
app/api/v1/batch/route.ts ALLOWED_PATH_PREFIXES (~38–51) lists /api/v1/reports, /tools, /schedules, etc. but omits /api/v1/calendar. First-class routes /api/v1/calendar/events and /api/v1/calendar/available-slots accept API keys independently. Batch POST returns 400 'path not allowed' for { method:'GET', path:'/api/v1/calendar/events' } — integrators must special-case calendar outside batch.
app/api/v1/batch/route.ts
Fix effort: Add '/api/v1/calendar' to ALLOWED_PATH_PREFIXES in batch/route.ts.
172v1 export 202 response documents the wrong poll URL
Email, v1 API & Entrée Panel Polish
mediumOpenTrivial fix
app/api/v1/export/route.ts (~236) tells clients to 'Poll GET /api/v1/export with the job ID'. Actual status endpoint is GET /api/v1/export/:id (app/api/v1/export/[id]/route.ts). API integrators following the message get 400/405 loops until they discover the correct path.
app/api/v1/export/route.tsapp/api/v1/export/
Fix effort: Fix message to GET /api/v1/export/{job_id} in export/route.ts.
173v1 POST /reports/analyze has no request body size cap
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenTrivial fix
Sibling app/api/v1/reports/route.ts rejects bodies >5MB via content-length (~43–52). v1/reports/analyze parses JSON with no size guard — only truncates data to 15k chars for the model (~137–141) after the full body is already in memory. Large API payloads can OOM serverless functions.
app/api/v1/reports/route.ts
Fix effort: Mirror v1 /reports content-length guard (~5MB) before JSON parse.
174v1 POST /tools has no code size limit
Build, IDE & Custom Tools
mediumOpenTrivial fix
app/api/v1/tools/route.ts handlePost (~88–96) accepts body.code string with no .length cap. Internal app/api/custom-tools/route.ts (~112–115) rejects code > 500KB. API-key consumers can INSERT multi-megabyte HTML into custom_tools.code — Postgres row bloat, slow list queries, potential Vercel body-size failures at edge.
app/api/v1/tools/route.tsapp/api/custom-tools/route.ts
Fix effort: Apply same 500KB code cap as session custom-tools route.
175v1 schedules PUT skips cron expression validation
Security & API Gaps (Pass 24)
mediumOpenTrivial fix
app/api/v1/schedules/route.ts defines isValidCron() (~23–26, 5-field check) and calls it in handlePost (~130). app/api/v1/schedules/[id]/route.ts handlePut (~106) assigns body.cron to cron_expression with zero validation — empty string, 'not-a-cron', or 100-field strings persist. job_schedules.cron_expression is plain text with no DB CHECK. Scheduler computing next_run_at from garbage crons fails silently for that row.
app/api/v1/schedules/route.tsapp/api/v1/schedules/
Fix effort: Share isValidCron() in PUT handler; add DB CHECK or cron parser library for semantic validation.
176Will-Call Window card opens staff admin, not public scheduler
Permissions, Settings & Misleading UI
mediumOpenTrivial fixMay affect neighbors
lib/greco/greco-tools.ts line 83 routes 'Will-Call Window' to /windowadmin. Customer/driver self-service booking lives at /window (WindowClient.tsx). Operators picking 'Window' from the tools grid land on the authenticated review UI; public /window is only linked from a small header on windowadmin. Misleading card for floor staff hunting the booking kiosk.
lib/greco/greco-tools.ts
Fix risk: Changing card href affects trained operators; /windowadmin vs /window is a product decision tied to permissions.
Fix effort: Point greco-tools card at /window or add second 'Window (public)' card.
177/api/admin/verify is an unauthenticated password oracle
Security & Trust Boundaries
mediumOpenEasy fix
app/api/admin/verify/route.ts accepts public POST { password } and returns { valid: true|false } against ADMIN_PASSWORD. Only generic per-IP rate limiting — no session requirement, lockout, or CAPTCHA. Credential stuffing against a shared admin password.
app/api/admin/verify/route.ts
Fix effort: Require authenticated session before comparing password, or remove endpoint and use server-only admin gate.
178/api/agent/chat accepts any sessionId without ownership verification
Agent, Collaboration & Platform APIs
mediumOpenEasy fix
app/api/agent/chat/route.ts (~168–188) inserts into ai_chat_messages using client-supplied sessionId after auth, without verifying the session exists or belongs to userInfo.companyId / userInfo.userId. Known or guessed UUIDs allow cross-session message injection and updated_at bumps on foreign sessions.
app/api/agent/chat/route.ts
Fix effort: Verify session belongs to userId + companyId before insert — one guard in agent/chat/route.ts.
179/api/email/send template whitelist rejects eight live EmailService templates
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fix
app/api/email/send/route.ts validTemplates (~60–64) lists 10 names; lib/email/service.ts EmailTemplate (~13–30) also defines ai_summary, anomaly_alert, agent_notification, agent_digest, dockvoo_checkin, item_scheduled, item_checked_in, item_received. Internal or agent callers using those template names get 400 Invalid template even though NotificationService can render them.
app/api/email/send/route.tslib/email/service.ts
Fix effort: Expand validTemplates in email/send/route.ts to match EmailTemplate union in lib/email/service.ts.
180/chat fetches AI sessions before checking auth — and never redirects
Auth & Session
mediumOpenEasy fix
app/chat/page.tsx loadData() calls getChatSessions(50) unconditionally whether or not a session exists. No /login redirect anywhere in the file. Logged-out visitors get the full chat shell with an empty list and a Supabase query on mount.
app/chat/page.tsx
Fix effort: Gate loadData on session + router.push('/login') — mirrors other pages.
181/confirmation shows verified-success UI without checking auth
Auth & Session
mediumOpenEasy fix
app/confirmation/page.tsx always renders 'Your email has been verified and your account is ready' with a dashboard link. No getSession() check, no Supabase auth listener — anyone who visits the URL sees a success screen whether or not they just confirmed email.
app/confirmation/page.tsx
Fix effort: Check for fresh recovery session or redirect; avoid showing success to random visitors.
182/inbox driver-chat queries ignore Supabase errors
Data Flows & Wiring
mediumOpenEasy fix
app/inbox/InboxClient.tsx loadThreads and loadMessages destructure only { data } and never inspect error. A failed query (RLS denial, network blip, stale token) renders as an empty thread list or empty conversation with no error banner — the page's error state is only used for send/AI failures.
app/inbox/InboxClient.tsx
Fix effort: Check error in loadThreads/loadMessages and set error state — InboxClient.tsx.
183/noe allows unlimited Loading Sheet reprints
Window, Sales & Kiosk Scheduling
mediumOpenEasy fix
Print-once guard in will-call-print-request/route.ts (~221–239) applies only when requester='sales'. /noe posts requester:'noe' (EntreeClient.tsx ~26503–26507) with only 20s dedupe. UI clears 'Queued' after 5s (~26520–26530) with no GET ?id= outcome polling — unlike /sales which polls until done/failed (~939–982). Operators can re-queue the same invoice indefinitely from /noe.
Fix effort: Extend print-once semantics to noe or track per-invno consumption like sales.
184/noe never polls print outcome despite bridge watcher existing
Window, Sales & Kiosk Scheduling
mediumOpenEasy fixMay affect neighbors
requestNoeLsPrint in EntreeClient.tsx (~26497–26530) sets 'Queued' then clears after 5s — no status poll. Bridge-side noe-print watcher (~27227+) fulfills rows but /noe UI never surfaces done/failed like /sales does. Comment at ~27238 claims /noe can show final status; client code does not implement it.
Fix risk: Adding poll changes kiosk CPU/network; removing watcher breaks silent print failures.
Fix effort: Poll will-call-print-request ?id= in requestNoeLsPrint like SalesClient requestPrint.
185/picture is a public unauthenticated internal-architecture diagram
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenEasy fix
app/picture/page.tsx (~127) labels the page PUBLIC • NO LOGIN. Documents ODBC/bridge data flow with PDF export via jspdf + html2canvas (~40–109). No auth, rate limit, or tenant gate — internal infra diagram exposed to anyone with the URL (linked from /port relay docs).
app/picture/page.tsx
Fix effort: Require session or move to /docs; rate-limit PDF export — product decision on public relay collateral.
186/port snapshot Shanetrée mirror is undiscoverable
Permissions, Settings & Misleading UI
mediumOpenEasy fix
/port wraps EntreeClient in portMode for ODBC-less devices. Grep for nav links to /port finds only picture/page.tsx, docs and an internal EntreeClient reference — not in GRECO_TOOLS, dashboard or greco-tools cards. Documented in /docs but absent from operational navigation; useful fallback with no way to find it.
Fix effort: Add GRECO_TOOLS card or dashboard Workspace link labeled 'Port mode (no ODBC)'.
187/produce standalone page duplicates Shanetrée ProducePanel
Dead Code & Orphan Features
mediumOpenEasy fixMay affect neighbors
app/produce/page.tsx (~800 lines) implements produce-item browsing via ODBC relay, but app/entree/ProducePanel.tsx line 6 says it was 'Ported from the standalone /produce page.' Grep finds no href/router links to /produce from live app pages — only the Entree panel and API paths remain. Entire route is a duplicate entry point with no navigation.
app/produce/page.tsxapp/entree/ProducePanel.tsx
Fix risk: Deduping parallel implementations must update every copy or behavior drifts again.
Fix effort: Delete app/produce/page.tsx and redirect /produce → /entree with produce panel open, or add nav link if standalone is intentional.
188/projects chat has no realtime subscription
Controls, Admin & Collaboration
mediumOpenEasy fixMay affect neighbors
app/projects/page.tsx loadProjectMessages (~48–58) runs once when a project expands — no postgres_changes subscription. Admin replies via GodView or dashboard won't appear until re-expand/refresh. Send failures are console.error only (~96–97).
app/projects/page.tsx
Fix risk: Realtime filter changes alter what live updates appear — easy to hide needed events.
Fix effort: Subscribe to project_messages postgres_changes when project expands.
189/sales search auto-triggers live ODBC invoice-status lookups
Window, Sales & Kiosk Scheduling
mediumOpenEasy fix
SalesClient.tsx (~1130–1148): when filtered rows are empty and the query looks like an invno, 600ms debounce calls runInvoiceLookup() which POSTs /api/public/invoice-status (~1017–1022) — direct ODBC or relay enqueue. Combined with unauthenticated, un-rate-limited endpoint, typo-sweeping on a shared kiosk can stack bridge jobs.
Fix effort: Require explicit Search button or throttle invoice-status POSTs per kiosk session.
190/scan has no authentication gate
Build, IDE & Custom Tools
mediumOpenEasy fix
app/scan/page.tsx never checks session or redirects to /login — only optionally loads getUserCompanyId for Greco tool filtering. Unauthenticated visitors can upload spreadsheets, run detection and router.push into warehouse tool routes (which may gate later).
app/scan/page.tsx
Fix effort: Redirect unauthenticated users to /login or require session before file upload.
191/scan mis-wires sessionStorage for three destination tools
Data Flows & Wiring
mediumOpenEasy fixMay affect neighbors
app/scan/page.tsx stores scannerFile in sessionStorage and redirects, but /reconciliation, /loader and /returns-intel never read that key (grep returns no matches). Nine other tools do consume it — users routed through scan to those three arrive with nothing loaded.
app/scan/page.tsx
Fix risk: Wiring reconciliation/loader/returns-intel to scannerFile changes upload path for users who worked around missing key.
Fix effort: Add scannerFile reader to reconciliation, loader, returns-intel pages (copy from forklift pattern) OR remove those three from scan routing table.
192/scan redirect can exceed sessionStorage quota
Build, IDE & Custom Tools
mediumOpenEasy fixMay affect neighbors
app/scan/page.tsx (~294–306) stores the entire file as a base64 data URL in sessionStorage scannerFile with no size check or try/catch around setItem. Large .xlsx files commonly exceed ~5MB quota → silent redirect with nothing loaded. Tracked mis-wire to three tools is a separate issue.
app/scan/page.tsx
Fix risk: Moving to IndexedDB or Storage changes large-file scan flow timing.
Fix effort: Size-check file before base64; use IndexedDB or server staging for large uploads.
193/windowadmin is not admin-gated despite the name
Window, Sales & Kiosk Scheduling
mediumOpenEasy fix
Route comment says 'Greco staff review' but access is GrecoOnlyGuard only (app/windowadmin/page.tsx). Unlike /controls founder/admin gate (ControlsClient.tsx), any Greco editor/viewer with a bookmark opens the appointment review console. Pairs with RLS allowing any member to accept/reject (#1 above).
app/windowadmin/page.tsx
Fix effort: Wrap /windowadmin in founder/admin role check like ControlsClient.
194Account chat and mention notification toggles are no-ops
Dead Code & Orphan Features
mediumOpenEasy fixHigh blast radius
Account settings expose chat_notifications and mention_notifications toggles (app/account/page.tsx) persisted to user_notification_preferences. lib/email/service.ts maps them to templates, but notifyChatMessage() and notifyMention() in lib/notifications/trigger.ts have zero callers — team-chat never triggers either. Users enable 'email me on mentions' and nothing ever sends. report_notifications still works via notifyReportGenerated; chat/mention prefs are decorative (~4 exported notify helpers, ~45 lines, never called).
app/account/page.tsxlib/email/service.tslib/notifications/trigger.ts
Fix risk: Wiring latent toggles suddenly enables or blocks behavior users assumed was active.
Fix effort: Call notifyChatMessage/notifyMention after send/mention, or hide toggles until wired.
195Account security_alerts toggle is decorative
Agent, API & Docs Integrity
mediumOpenEasy fixHigh blast radius
account/page.tsx exposes security_alerts in NOTIFICATION_OPTIONS (~43–46) with copy 'You will always receive critical security notifications' (~1199–1200). grep sendSecurityAlert finds only the definition in lib/email/service.ts (~306) — zero callers. Toggle persists to user_notification_preferences but nothing ever sends security_alert emails.
lib/email/service.ts
Fix risk: Wiring latent toggles suddenly enables or blocks behavior users assumed was active.
Fix effort: Wire sendSecurityAlert on password change/login anomalies, or hide toggle.
196account_geocodes has no foreign key on company_id
Backend & Database
mediumOpenEasy fixMay affect neighbors
supabase/migrations/136_create_account_geocodes.sql:20 defines company_id uuid not null with no REFERENCES companies(id) — header comment promises multi-tenant scoping but FK omitted. RLS policies (~37–56) filter by profiles.company_id; orphaned UUIDs from bad imports pass INSERT and pollute Window Map geocode cache keys (company_id, custno) unique index.
supabase/migrations/136_create_account_geocodes.sql
Fix risk: Adding FK fails migration if orphan rows exist — must clean Window Map cache first.
Fix effort: New migration: ADD CONSTRAINT REFERENCES companies(id) — verify no orphan rows first.
197Agent policy scheduling windows evaluate in UTC only
Agent, API & Docs Integrity
mediumOpenEasy fixHigh blast radius
lib/agent/policy.ts isWithinPolicyWindow() (~167–177) uses getUTCDay(), getUTCHours() and getUTCMinutes(). Policy carries scheduling.timezone (~114) and dry-run returns it (app/api/agent/policy/schedules/dry-run/route.ts ~40), but window checks ignore the configured timezone. Autonomy cron + policy dry-run can disagree with US warehouse hours operators think they configured.
lib/agent/policy.tsapp/api/agent/policy/schedules/dry-run/route.ts
Fix risk: Date boundary changes reorder stats, leaderboards, and filters keyed on existing date columns.
Fix effort: Convert at to policy.scheduling.timezone before day/hour/minute check in isWithinPolicyWindow.
198Agent query tools accept uncapped limit parameters
Performance & Scale
mediumOpenEasy fix
lib/agent/tools.ts queryReports() passes args.limit straight to .limit(limit) with no clamp (executeTool default 50 only when arg omitted). A single agent tool call with limit: 50000 pulls full JSONB report rows for a tenant via service-role client — parallel to tracked internal API limit gaps.
lib/agent/tools.ts
Fix effort: Math.min(args.limit ?? 50, 200) in executeTool before queryReports and sibling query tools.
199agent_notification emails embed unvalidated actionUrl in button href
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fix
lib/email/service.ts (~491–493) uses ${data.actionUrl} in an anchor with no same-origin or https allowlist. Agent-supplied or forged actionUrl values can become open redirects or phishing links inside FileDisplay-branded notification emails.
lib/email/service.ts
Fix effort: Allowlist actionUrl to same-origin paths or https://filedisplay.com/* before rendering button.
200Agent-autonomy cron silently caps at 100 companies per run
API & Operations
mediumOpenEasy fix
app/api/cron/agent-autonomy/route.ts (~33–37) selects agent_settings rows with auto_summarize or auto_email_digest, then .limit(100) — no .range() pagination, no overflow counter in JSON response. vercel.json schedules */10; companies alphabetically beyond row 100 silently never enqueue digest agent_tasks. GodView tenant list can exceed 100 without any cron metric surfacing the skip.
app/api/cron/agent-autonomy/route.ts
Fix effort: Paginate settings query or raise limit with overflow logging in JSON response.
201Analytics-log date filters use UTC midnight boundaries
Greco Warehouse Operations
mediumOpenEasy fixHigh blast radius
GET /api/greco/analytics-log applies date_from/date_to as `${date}T00:00:00.000Z` and `T23:59:59.999Z` (~35–39). Eastern evening report saves (local Jul 9 ~8pm) store created_at as Jul 10 UTC and fall outside a Jul 9 filter on the Daily Stats Analytics Log — separate from report_date stamping but same operator confusion on date pickers.
Fix risk: Date boundary changes reorder stats, leaderboards, and filters keyed on existing date columns.
Fix effort: Filter on report_date or warehouse-local day bounds instead of created_at Z midnight.
202API key cache invalidation flushes all tenants
API & Operations
mediumOpenEasy fixMay affect neighbors
lib/cache/service.ts apiKeyCache.invalidateCompany(companyId) (~426–428) accepts companyId but calls cacheDeletePattern('fd:api_key:*') — wipes every tenant's cached API keys on one company's key rotation. Comment at ~402 admits 'can't easily find all users in a company'. invalidateApiKeyCache(keyHash) correctly deletes one hash (~510–511). Cross-tenant cache flush on single-key revoke is a multi-tenancy footgun.
lib/cache/service.ts
Fix risk: Duplicate — global pattern delete affects every API consumer simultaneously.
Fix effort: Scope cacheDeletePattern to companyId in lib/cache/service.ts; add company check on invalidate_user.
203Build page progress timers leak on failure
Wiring & Broken Flows
mediumOpenEasy fix
The tool-generation flows in app/build/page.tsx stage fake pipeline progress with four chained setTimeouts, cleared only on the success path — the catch blocks never clear them, and neither does unmount. A failed generation (or navigating away mid-request) leaves orphan timers calling setCurrentStep on a dead flow. Same pattern in generateFromPrompt and refineCode.
app/build/page.tsx
Fix effort: Same as build/unmount timer leak — clear timeout refs in catch + useEffect cleanup.
204Build page progress timers leak on failure/unmount
Data Flows & Wiring
mediumOpenEasy fix
app/build/page.tsx chains four setTimeout calls for fake pipeline progress in generateFromFile, generateFromPrompt and refineCode. Timeouts cleared only on success path; catch blocks don't clear them and there's no unmount cleanup. Failed generation or navigating away leaves orphan timers calling setCurrentStep on dead state.
app/build/page.tsx
Fix effort: Store timeout IDs in ref, clear in catch + useEffect cleanup — ~3 functions in build/page.tsx.
205Calendar has no auth wall
Auth & Session
mediumOpenEasy fixCritical — test everything
app/calendar/page.tsx never redirects unauthenticated users. It loads events, renders the full calendar UI, and only skips setting currentUserId when getUser() returns null. Logged-out visitors see an empty calendar shell and can open the event form — createCalendarEvent will fail at RLS, but the page shouldn't render at all.
app/calendar/page.tsx
Fix risk: Central auth changes affect every protected route and public kiosk exception list.
Fix effort: Add same auth redirect pattern as account/page.tsx at top of calendar load effect.
206Calendar realtime ignores Personal vs Company tab filter
Controls, Admin & Collaboration
mediumOpenEasy fixMay affect neighbors
getCalendarEvents(..., calendarTab) filters personal vs company in lib/supabase/calendar.ts (~93–108). subscribeToCalendarEvents in calendar/page.tsx (~542–546) inserts every calendar_events row for the company with no calendarTab, date-range or visibility filter. On Personal tab, colleagues' company events appear live incorrectly.
lib/supabase/calendar.ts
Fix risk: Filtering subscription may hide legitimate cross-tab updates users currently see (and complain about).
Fix effort: Filter realtime inserts/updates in calendar/page.tsx by calendarTab visibility rules.
207Calendar shows Edit/Delete on every visible event, not just owned events
Agent, Collaboration & Platform APIs
mediumOpenEasy fix
app/calendar/page.tsx EventDetail (~421–427) renders edit/delete for all expanded events with no created_by === currentUserId check. RLS allows DELETE only for creator (088_create_calendar_events.sql ~70–76); handleDelete (~571–574) calls deleteCalendarEvent and reloads without surfacing RLS failure — operators think they deleted a coworker's event when nothing changed.
app/calendar/page.tsx
Fix effort: Hide edit/delete unless event.created_by === user.id; surface RLS error toast on failed delete.
208Calendar sub-toolbar overflows on narrow screens
UI, Flow & Density
mediumOpenEasy fix
app/calendar/page.tsx sub-nav (~627–680) is flex justify-between with calendar tabs, view-mode toggle and month prev/next in one row with no flex-wrap. On narrow viewports the month navigation and view controls collide with Personal/Company tabs — horizontal overflow or clipped controls.
app/calendar/page.tsx
Fix effort: flex-wrap sub-nav or collapse month nav into a compact dropdown below md breakpoint.
209Calendar swallows every failure
Data Flows & Wiring
mediumOpenEasy fixMay affect neighbors
lib/supabase/calendar.ts getCalendarEvents returns [] on error. handleSave catches with console.error only; handleDelete has no try/catch. Network blip during save/delete looks like success; failed load looks like an empty calendar.
lib/supabase/calendar.ts
Fix risk: Surfacing errors changes empty-state UX — operators may think calendar is broken when network blips.
Fix effort: Add error state banner + surface calendar.ts errors instead of returning [].
210calls table (WebRTC) was never integrated
Dead Code & Orphan Features
mediumOpenEasy fix
supabase/migrations/081_create_calls.sql creates a calls table with realtime publication. Grep for .from('calls') across the entire repo returns zero matches. calendar_events.call_type (migration 088) is similarly unused in app code. ~45 lines of schema + RLS for a video-call feature that was scaffolded and abandoned.
supabase/migrations/081_create_calls.sql
Fix effort: Drop calls table + call_type column in a migration, or scope a minimal WebRTC spike before keeping schema.
211Chat Build mode iframes /vibe with a misleading title
UI, Flow & Density
mediumOpenEasy fix
app/chat/page.tsx Greco-only viewMode === 'vibe' renders <iframe src='/vibe' title='Build — Tool Builder'> (~1008–1014) while the tab label says Build. /vibe is the web studio; /build is the custom-tool file/prompt builder — the iframe title and tab name imply the wrong product surface.
app/chat/page.tsx
Fix effort: Point iframe at /build or rename tab to Vibe; fix title attribute to match actual src.
212client_events telemetry table has no retention or cleanup path
Agent, Collaboration & Platform APIs
mediumOpenEasy fix
supabase/migrations/107_create_ide_context_index_and_client_events.sql creates client_events with INSERT RLS. Writer is app/api/telemetry/client-event/route.ts only. No cron, TTL index, partition strategy or cleanup migration — table grows unbounded as IDE/desktop extension telemetry accumulates.
supabase/migrations/107_create_ide_context_index_and_client_events.sqlapp/api/telemetry/client-event/route.ts
Fix effort: Cron deleting rows older than 90d or partition by month — migration + vercel.json cron entry.
213companyCache and statsCache are write-only — only invalidate is called
Dead Code Hunt (Pass 22)
mediumOpenEasy fix
lib/cache/service.ts defines companyCache.get/.set (~351–365) and statsCache.get/.set (~458–467). Grep companyCache|statsCache across the repo finds only invalidateCompanyCache() calling .invalidate / .invalidateAll (~500–502). No .get or .set callers anywhere — ~120 lines of cache objects that never store or read data; invalidation runs against empty caches.
lib/cache/service.ts
Fix effort: Wire .get/.set at read sites or delete cache objects and simplify invalidateCompanyCache.
214CONTROLLABLE_TABS preset list is also stale
Permissions, Settings & Misleading UI
mediumOpenEasy fix
CONTROLLABLE_TABS (~64–74) still lists members, chat, history and stats as dashboard tabs and feeds getDefaultDashboardTabs() for permission presets. Any future wiring of dashboard_tabs would gate the wrong tab IDs — same IA drift as the Account/Controls visibility toggles.
Fix effort: Same realignment as tab visibility — single source of truth in lib/dashboard/tabs.ts.
215Controls member-permissions UI omits dashboard_tabs entirely
Permissions, Settings & Misleading UI
mediumOpenEasy fix
MemberPermissionsSection saves tool_access, tool_features and three edit flags (~79–85) but never renders or writes dashboard_tabs even though updateMemberPermissions supports it. Founders cannot configure per-member tab ACL in the admin UI that was built for it — paired with the dashboard never reading the field.
Fix effort: Add dashboard_tabs matrix to MemberPermissionsSection using updated CONTROLLABLE_TABS.
216Daily Stats date picker navigates in UTC, not warehouse local
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fixMay affect neighbors
app/daily-stats/DailyStatsClient.tsx fmtDate() uses d.toISOString().split('T')[0] (~84–85) for selectedDate, week navigation (~731–733) and Today (~866). After ~7–8pm Eastern, 'today' becomes tomorrow UTC — parallel to tracked Shanetrée/DockSheet UTC issues but specific to /daily-stats operator date picking.
app/daily-stats/DailyStatsClient.tsx
Fix risk: Aligning to local TZ must match report_date columns in greco_daily_stats or grid shows empty days.
Fix effort: Use America/New_York (or company timezone setting) for fmtDate — same fix class as Greco UTC dates.
217Dashboard email and report modals lack dialog accessibility
UI, Flow & Density
mediumOpenEasy fix
Email Settings modal (~2837+) and Per-Report Settings modal (~3154+) are fixed inset-0 overlays with no role='dialog', aria-modal, aria-labelledby, Escape-to-close or focus trap. Same gap class as team-chat/developer modals already tracked — but these two high-traffic dashboard modals were missed.
Fix effort: Add role=dialog, aria-modal, Escape and focus trap to emailSettingsModal and reportSettingsModal overlays.
218Dashboard mentions have no realtime subscription
Agent, API & Docs Integrity
mediumOpenEasy fixMay affect neighbors
dashboard/page.tsx calls loadMyMentions() / loadUnreadMentionCount() once on mount (~436–437, ~914–932). No postgres_changes on team_mentions (contrast team-chat subscribeToChatRoomMessages and calendar subscriptions). New @mentions require manual refresh or navigating away — badge can stay stale on a long-lived dashboard session.
Fix risk: Realtime filter changes alter what live updates appear — easy to hide needed events.
Fix effort: postgres_changes on team_mentions filtered by mentioned_user_id; refresh badge on insert.
219Dashboard never subscribes to AI conversation comments
Data Flows & Wiring
mediumOpenEasy fix
subscribeToAIConversationComments() exists in lib/supabase/ai-conversations.ts but is never imported. Dashboard subscribes to new conversations via subscribeToAIConversations only — new comments on existing conversations won't appear until manual refresh or the 30s polling interval.
lib/supabase/ai-conversations.ts
Fix effort: Import and wire subscribeToAIConversationComments alongside existing conversation subscription.
220Dashboard uses blocking alert() for errors
UI, Flow & Density
mediumOpenEasy fix
app/dashboard/page.tsx still calls alert() for AI-filter save failures, external-email add/remove, file download errors, AI summary failures and quote submission accept/reject (~619–1409). Blocking native dialogs interrupt warehouse workflows, can't be styled or translated consistently, and feel out of place beside the polished glass UI elsewhere.
app/dashboard/page.tsx
Fix effort: Replace ~10 alert() calls with inline toast/banner state — reuse existing dashboard error UI patterns.
221Developer page load failures show empty lists with no error
Data Flows & Wiring
mediumOpenEasy fix
app/developer/page.tsx loadApiKeys and loadWebhooks catch errors with console.error only. UI renders 'No API Keys' / 'No Webhooks' with no error banner — identical to a real empty integration setup. Tier banner failures are also swallowed.
app/developer/page.tsx
Fix effort: Surface fetch errors inline on developer/page.tsx instead of empty-state copy.
222Developer settings modals lack dialog accessibility
UI, Colors & Aesthetics
mediumOpenEasy fix
app/developer/page.tsx modals at ~1039+ (showCreateKeyModal, webhook create/details) use fixed inset-0 bg-black/50 overlays with inner white panel — backdrop click and X close only. grep role='dialog' / aria-modal / onKeyDown Escape in developer/page.tsx returns zero. Same mouse-only pattern as calendar EventFormModal and members overlay.
app/developer/page.tsx
Fix effort: Same dialog pattern on three developer/page.tsx modals.
223Developer webhook create accepts arbitrary URL schemes
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fix
app/developer/page.tsx (~358–369) sends newWebhookUrl.trim() to POST /api/settings/webhooks with no https:// requirement or SSRF blocklist. javascript: or internal host URLs can be stored as webhook targets — delivery worker may fail or hit unexpected endpoints when triggered.
app/developer/page.tsx
Fix effort: Require https:// and block private IP ranges on POST /api/settings/webhooks + developer UI validation.
224digest_recipients and digest_day_of_week are persisted but unused
Agent, API & Docs Integrity
mediumOpenEasy fix
Fields exist in lib/agent/permissions.ts (~29–30, 114–115) and lib/agent/settings-shared.ts, and appear in OpenAPI agent settings schema. AgentSettingsPanel has no recipient picker or day-of-week control; cron/worker ignore both. OpenAPI types digest_day_of_week as string; code uses number — schema drift on top of dead fields.
lib/agent/permissions.tslib/agent/settings-shared.ts
Fix effort: Add recipient/day UI in AgentSettingsPanel + read fields in cron/worker; fix OpenAPI type to number.
225Dock check-in notifications can be spammed
Rate Limits & Abuse Surface
mediumOpenEasy fix
/api/dockvoo/notify loops the company's notification preferences and sends an email per subscriber (plus per-PO bridge relay calls for time_in/docked events) on every POST — authenticated, but with no rate limit. Any company member can hammer colleagues' inboxes and the bridge with repeated check-in events.
Fix effort: Per-user rate limit on /api/dockvoo/notify.
226DockSheet bootstraps workspace date in UTC
Greco Warehouse Operations
mediumOpenEasy fixHigh blast radius
CindyVueClient.tsx session bootstrap (~1277–1332) sets const today = new Date().toISOString().slice(0, 10) when picking the active workspace tab. Combined with global LS_DATE_KEY ('cindyVueLastDate' in lib/tools/greco/lib/constants.ts), a receiver opening DockSheet near midnight UTC boundary can land on the wrong session_date relative to the warehouse clock.
lib/tools/greco/lib/constants.ts
Fix risk: Date boundary changes reorder stats, leaderboards, and filters keyed on existing date columns.
Fix effort: Use warehouse-local date helper in CindyVueClient bootstrap; align with session_date convention.
227DockSheet Live defaults to UTC today, not warehouse local date
Data Flows & Wiring
mediumOpenEasy fixHigh blast radius
app/live/greco/page.tsx picks today via new Date().toISOString().slice(0, 10) (UTC). Sibling /timer uses localTodayStr() (browser local). After ~7–8pm Eastern, Live can open yesterday's session while Timer opens today's — inconsistent kiosk behavior.
app/live/greco/page.tsx
Fix risk: Date boundary changes reorder stats, leaderboards, and filters keyed on existing date columns.
Fix effort: Reuse localTodayStr() from timer or America/New_York helper for initial selectedDate.
228DockSheet Live swallows session load failures as empty data
Data Flows & Wiring
mediumOpenEasy fixCritical — test everything
app/live/greco/page.tsx loadSession() and the dates bootstrap use if (!res.ok) return and empty catch blocks. Network/500 failures render 'No data for this date' — indistinguishable from a genuinely empty dock day. No error banner or retry beyond manual refresh.
app/live/greco/page.tsx
Fix risk: DockSheet session stack — autosave, grid, and public kiosks depend on current save semantics.
Fix effort: Add error state + retry banner in app/live/greco/page.tsx when fetch fails.
229DockSheet notif-prefs GET leaks coworker auth emails
Security & Trust Boundaries
mediumOpenEasy fix
app/api/dockvoo/notif-prefs/route.ts GET returns companyLog with every watched PO plus user_id, user_email and user_name for all enabled company members. Any authenticated tenant user can enumerate colleagues' auth emails via admin.auth.admin.getUserById in the loop at lines 82–88.
app/api/dockvoo/notif-prefs/route.ts
Fix effort: Return only caller's prefs on GET; move companyLog to admin-only endpoint or strip emails.
230DockSheet workspace localStorage is not namespaced per operator
Greco Warehouse Operations
mediumOpenEasy fixMay affect neighbors
Beyond tracked kiosk theme keys: CindyVue persists cindyVueLastDate (LS_DATE_KEY), per-day session blobs and workspace tab lists in localStorage with no userId suffix. Shared receiving PCs inherit the previous receiver's active date and offline backup payload — can restore the wrong day's grid after shift change.
Fix risk: Receiver desk PCs share layout/date prefs — namespacing changes which session date opens by default.
Fix effort: Prefix LS_DATE_KEY and per-day keys with userId or shift token on shared PCs.
231Documents create modal lacks dialog accessibility
UI, Flow & Density
mediumOpenEasy fix
app/documents/page.tsx Create Document overlay (~1162–1286) is a fixed inset-0 div with backdrop click and X to close — no role='dialog', aria-modal, Escape handler or focus trap. Fourth high-traffic modal in the same accessibility gap class (after calendar, members, team-chat, developer).
app/documents/page.tsx
Fix effort: Add role=dialog, aria-modal, Escape and focus trap to showCreateModal overlay.
232Documents editor: debounce timers survive unmount
Data Flows & Wiring
mediumOpenEasy fix
app/documents/page.tsx schedules typing-indicator, content-diff and history snapshots through timeout refs with no unmount cleanup. Leaving mid-edit can fire broadcasts and setState on an unmounted component. Unsaved-changes confirm() guards in-app nav only — no beforeunload for tab close/refresh.
app/documents/page.tsx
Fix effort: useEffect cleanup clearing three timeout refs + optional beforeunload — documents/page.tsx only.
233Documents editor: debounce timers survive unmount, and only in-app nav guards unsaved edits
Wiring & Broken Flows
mediumOpenEasy fix
app/documents/page.tsx schedules typing-indicator, content-diff and history snapshots through three timeout refs with no unmount cleanup — leaving the page mid-edit can fire broadcasts and setState on an unmounted component. Separately, the unsaved-changes confirm() only guards in-app doc switching and back-to-list; there's no beforeunload handler, so a tab close or refresh silently discards long-form edits.
app/documents/page.tsx
Fix effort: Same cleanup + beforeunload.
234Driver Scan loads ExcelJS from unpinned cdnjs without SRI
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fixMay affect neighbors
app/driver-scan/DriverScanClient.tsx (~67) injects https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.3.0/exceljs.min.js with no integrity attribute. Parallel supply-chain risk to tracked Supabase CDN pin in app/layout.tsx — dock-floor machines parse driver scan xlsx through a third-party script tag.
app/driver-scan/DriverScanClient.tsxapp/layout.tsx
Fix risk: Pinning/SRI is safe; swapping loader breaks offline dock tablet if CDN blocked.
Fix effort: Bundle exceljs via npm import or add SRI + version pin — match Entree dynamic import pattern.
235driver_chat_settings UPDATE RLS allows any company member
Shanetrée, Routing & Driver Comms
mediumOpenEasy fix
supabase/migrations/129_create_driver_chat.sql lines 132–135: UPDATE policy checks only company_id membership, not role. /inbox AI toggles write driver_chat_settings.ai_enabled_all and response_mode — UI implies an operational control, but schema lets any member including viewers change global driver-chat AI behavior via Supabase client or inbox UI.
supabase/migrations/129_create_driver_chat.sql
Fix effort: Tighten UPDATE policy to manager+ roles; mirror in API route checks.
236Driver-chat AI controls have no role gate
Shanetrée, Routing & Driver Comms
mediumOpenEasy fix
requireGrecoUser() in lib/driver-chat/db.ts (~86–104) only checks profile.company_id === GRECO_COMPANY_ID — any Greco member including viewers can hit /api/driver-chat/ai, /api/driver-chat/settings and manual ai-respond. Combined with driver_chat_settings UPDATE RLS allowing any company member, floor viewers can flip global AI auto-reply without founder/admin role.
lib/driver-chat/db.ts
Fix effort: Restrict /api/driver-chat/settings and ai-respond to founder/admin/manager roles.
237Driver-chat unread counters are a lost-update waiting to happen
Data Integrity & Races
mediumOpenEasy fix
postMessage() in lib/driver-chat/db.ts sets greco_unread / driver_unread to (staleThreadObject.unread || 0) + 1 — a read-modify-write from a thread object fetched earlier in the request. Two messages arriving close together on one thread can produce a count of 1 instead of 2. Should be an atomic UPDATE … SET greco_unread = greco_unread + 1 (or an RPC).
lib/driver-chat/db.ts
Fix effort: Same atomic increment.
238Driver-chat unread counters use stale read-modify-write
Backend & Database
mediumOpenEasy fix
lib/driver-chat/db.ts postMessage() (~186–215) inserts message then sets greco_unread = (thread.greco_unread || 0) + 1 using thread object passed from caller's earlier SELECT. Concurrent messages on one thread (two drivers, AI + human) race read-modify-write — count can stay at 1 when 2 arrived. Fix: atomic UPDATE driver_chat_threads SET greco_unread = greco_unread + 1.
lib/driver-chat/db.ts
Fix effort: Atomic UPDATE SET greco_unread = greco_unread + 1 in lib/driver-chat/db.ts or small RPC.
239Drivers, Transfers and Cycle Count skip getToolPagePermissions
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenEasy fix
app/drivers/DriversClient.tsx, app/transfers/TransfersClient.tsx and app/cyclecount/CycleCountClient.tsx have no getToolPagePermissions import. Viewers get full generate/export. Geronimo permission gap is tracked — these three warehouse tools share the same inconsistency with the /controls permission matrix.
app/drivers/DriversClient.tsxapp/transfers/TransfersClient.tsxapp/cyclecount/CycleCountClient.tsx
Fix effort: Wire getToolPagePermissions gate on export/generate buttons — copy negative-slots pattern per client.
240Dual React component roots — components/ vs app/components/
Repository Structure Audit (Pass 23)
mediumOpenEasy fix
app/components/ holds 24+ live UI modules (FancyLoader, GodView, GrecoOnlyGuard, etc.) imported across the app. Root components/ holds only ShanetreeWhitepaperSections.tsx, ShanetreeWhitepaperGuideSections.tsx and components/docs/ShanetreeDocsSection.tsx — imported via @/components/ from EntreeClient and app/docs. tsconfig paths map @/* to ./* so both work, but the split confuses every new contributor ('where do I add a component?'). Consolidate under app/components/ or src/components/ with one rule.
app/components/app/docs
Fix effort: Move three root components/ files into app/components/docs/; update @/ imports.
241Duplicate entry points to the same destinations
Navigation & Flow
mediumPartly fixedEasy fixMay affect neighbors
DockSheet, Guide and Shanetrée each exist as an overview card AND a tab-bar link; AI and Docs are Workspace-folder cards and tab-bar links. The non-Greco overview no longer duplicates AI/Docs/Reports (they live in the Workspace folder now), and the remaining tab-bar links are marked with ↗ as shortcuts. The card is the canonical home; the tab strip is a quick-jump row.
Fix risk: Deduping parallel implementations must update every copy or behavior drifts again.
Fix effort: IA cleanup — mostly done; remaining overview card vs tab-bar shortcut duplication.
242Forklift embedding index fails silently
Permissions, Settings & Misleading UI
mediumOpenEasy fix
Only app caller of /api/embeddings is forklift/page.tsx (~699–710): fetch with .catch(() => {}) and no response.ok check. Missing OpenAI key or route errors mean vector indexing fails invisibly — semantic search/RAG for forklift reports never updates with no operator feedback.
Fix effort: Check response.ok in forklift fetch; optional toast when embedding index fails.
243Four team-activity query helpers are unused
Dead Code & Orphan Features
mediumOpenEasy fix
lib/supabase/team-activity.ts exports getTeamActivityByTool, getTeamActivityByUser, getAnalyticsLog and toggleActivityExcluded (~120 lines) with zero imports. AnalyticsLogPanel bypasses getAnalyticsLog and fetches /api/greco/analytics-log directly (app/components/AnalyticsLogPanel.tsx:104). Built for admin drill-downs that were never surfaced in UI.
lib/supabase/team-activity.tsapp/components/AnalyticsLogPanel.tsx
Fix effort: Delete helpers or expose in Controls/History admin UI if still wanted.
244generate-from-file accepts unbounded fileProfile JSON
Build, IDE & Custom Tools
mediumOpenEasy fixMay affect neighbors
app/api/custom-tools/generate-from-file/route.ts (~281) parses body with no content-length cap — unlike generate/route.ts (~386–395) which caps at 1MB. Build sends the full client profile (app/build/page.tsx ~643) enabling oversized payloads and memory pressure server-side.
app/api/custom-tools/generate-from-file/route.tsapp/build/page.tsx
Fix risk: Adding caps breaks export or load-all flows that depend on fetching entire tables.
Fix effort: Cap request body size like generate route; reject oversized profiles early.
245Geocoding route is an open proxy
Security & Trust Boundaries
mediumOpenEasy fixMay affect neighbors
app/api/routing/geocode/route.ts GET (~6–34) accepts any ?q= string, forwards to nominatim.openstreetmap.org with FileDisplay User-Agent, no auth, no withRateLimit. Used by app/tools/greco/routing/RoutingClient.tsx. Abusable as free geocoding relay under FileDisplay IP; Nominatim ToS requires rate limiting — this route has none.
app/api/routing/geocode/route.tsapp/tools/greco/routing/RoutingClient.tsx
Fix risk: Auth or 1 req/sec limit breaks Window Map modal batch geocode on account roster open — Nominatim throttle already painful.
Fix effort: Same rate limit + auth on /api/routing/geocode.
246Geocoding route is an open, unauthenticated proxy
Security & Trust Boundaries
mediumOpenEasy fixMay affect neighbors
app/api/routing/geocode/route.ts GET (~6–34): no withRateLimit, no getUser()/session check — any caller passes ?q=, server forwards to nominatim.openstreetmap.org with FileDisplay User-Agent. RoutingClient.tsx and account-geocodes Window map depend on it. Abusable as free geocoding relay under FileDisplay egress IP; Nominatim usage policy expects ≤1 req/sec per app.
app/api/routing/geocode/route.ts
Fix risk: Same routing geocode proxy — RoutingClient and account-geocodes depend on unauthenticated access.
Fix effort: Add withRateLimit + requireAuth (or internal token); optional response caching.
247Geronimo skips getToolPagePermissions — viewers get full export
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenEasy fix
Unlike app/negative-slots/page.tsx (~112–117) and app/licenses/page.tsx (~127–130), app/geronimo/GeronimoClient.tsx has no analytics/AI permission gate. Every Greco member including viewers always gets full generate/print/export — inconsistent with permission matrix on sibling slot tools.
app/negative-slots/page.tsxapp/licenses/page.tsxapp/geronimo/GeronimoClient.tsx
Fix effort: Wire getToolPagePermissions('Geronimo') like negative-slots — gate export buttons.
248GET /api/dashboard/custom-cards loads every tenant's cards into memory
Agent, Collaboration & Platform APIs
mediumOpenEasy fix
app/api/dashboard/custom-cards/route.ts (~48–66) runs select('*') on custom_dashboard_cards with no company_id SQL filter, then filters in JS by target_user_ids / target_company_ids. Service-role query pulls cross-tenant rows on every dashboard load — multi-tenancy leak in memory plus unnecessary scale cost as card count grows.
app/api/dashboard/custom-cards/route.ts
Fix effort: Add .or(target_user_ids.cs.{id},target_company_ids.cs.{companyId}) SQL filter — stop full-table select.
249GET /api/greco/session?list=dates leaks up to 200 session dates without auth
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenEasy fixHigh blast radius
app/api/greco/session/route.ts (~23–33) returns session_date + updated_at for the 200 most recent DockSheet days with no authentication. /timer bootstrap calls this (~343–347). Remote callers can fingerprint which warehouse days had receiving activity without loading appointment payloads.
app/api/greco/session/route.ts
Fix risk: Removing or gating list=dates breaks date-picker UX in timer/inbound that enumerates sessions without login.
Fix effort: Require session or CRON_SECRET for list=dates; or return only last 7 days for public timer.
250GodView project and support chat filters run client-side after fetch
Controls, Admin & Collaboration
mediumOpenEasy fix
getProjectChats and getSupportThreads (~437–494) query without company_id, then filteredData = filteredData.filter(...) in JS. Fetches up to limit cross-tenant rows then discards — wrong total, wasted payload when a company filter is active.
Fix effort: Filter project_messages/support_messages by company_id in SQL before range().
251GodView updateUserRole has no target validation
Controls, Admin & Collaboration
mediumOpenEasy fix
app/api/admin/godview/route.ts updateUserRole (~893–900): service-role .from('profiles').update({ role: params.role }).eq('id', params.userId) — no verify target belongs to caller's company, no role enum whitelist in function (only HTTP handler trusts client JSON). Platform admin typo on userId can re-role another tenant's account.
app/api/admin/godview/route.ts
Fix effort: Verify target profile.company_id and whitelist allowed roles before update.
252Greco digest cron uses UTC 'yesterday,' not warehouse local
API & Operations
mediumOpenEasy fixHigh blast radius
app/api/cron/greco-digest/route.ts derives yesterdayStr from new Date().toISOString().split('T')[0] in UTC. For an Eastern warehouse, the 05:00 UTC cron (vercel.json) can digest the wrong calendar day relative to dock operations.
app/api/cron/greco-digest/route.ts
Fix risk: Changing digest window shifts which stats rows email includes — ops runbooks assume current behavior.
Fix effort: Use America/New_York date helper in greco-digest cron (same as forklift fix).
253greco_card_permissions fails open on query errors
Greco Warehouse Operations
mediumOpenEasy fixHigh blast radius
getMyGrecoCardDenials() in lib/supabase/greco-card-permissions.ts (~113–117) returns empty denial sets (full access) when the Supabase select errors — deliberate comment 'never accidentally hide tools.' Inverse risk: a transient RLS/network failure shows every denied tool/card again, defeating permission matrices until the query succeeds.
lib/supabase/greco-card-permissions.ts
Fix risk: Fail-closed denies entire tool grid when Supabase blips — dock shift starts with empty Tools tab.
Fix effort: Fail closed to last-known denials in sessionStorage, or show 'permissions unavailable' and block tools tab.
254greco_routes.company_id has no foreign key to companies
Shanetrée, Routing & Driver Comms
mediumOpenEasy fixMay affect neighbors
supabase/migrations/114_create_greco_routes.sql line 18 defines company_id uuid not null with no REFERENCES companies(id). Same migration-hygiene class as tracked account_geocodes FK gap but greco_routes is the live routing planner table — orphaned UUIDs would pass RLS checks that compare to profiles.company_id only.
supabase/migrations/114_create_greco_routes.sql
Fix risk: Adding FK on live data fails if orphan rows exist — needs cleanup migration first.
Fix effort: Migration: REFERENCES companies(id) ON DELETE CASCADE after verifying live UUID types.
255greco-digest cron TOOL_COLUMNS omits half the live Greco tool suite
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenEasy fix
app/api/cron/greco-digest/route.ts TOOL_COLUMNS (~27–31) lists dockvoo through driver_scan but omits geronimo, licenses, negative_slots, empty_slots, thruput, meat_count, incentives, items, schedule, etc. Daily digest agent_memory and anomaly baselines under-report which tools actually ran — even when those tools write greco_daily_stats.
app/api/cron/greco-digest/route.ts
Fix effort: Expand TOOL_COLUMNS to match DailyStatsClient TOOL_META keys — config change in greco-digest route.
256Greco-digest silently skips days with no greco_daily_stats row
Agent, API & Docs Integrity
mediumOpenEasy fix
app/api/cron/greco-digest/route.ts (~70–74) returns { success: true, message: 'No stats found… skipping' } when yesterday's stats row is missing. No retry queue or alert. If stats aggregation lags the cron, that day's digest/anomalies never land in agent_memory — quiet data loss.
app/api/cron/greco-digest/route.ts
Fix effort: Retry next run or alert when stats missing; don't return success: true on skip.
257History load errors render as 'no activity'
Data Flows & Wiring
mediumOpenEasy fix
app/history/page.tsx load() catch (~103–104) and changeLoadLimit catch (~120–121) only console.error — never setLoadError state. HistoryLedger renders 'No activity recorded yet' (HistoryLedger.tsx ~545) when teamActivity=[]. Transient Supabase/RLS failure looks like quiet day. Export path (~181) sets exportError banner — load errors should match.
app/history/page.tsx
Fix effort: Add error banner like export path already has — history/page.tsx.
258History page load errors render as “no activity”
Wiring & Broken Flows
mediumOpenEasy fix
The initial and paginated loads in app/history/page.tsx catch errors with console.error only, then render the empty ledger. A transient Supabase failure is indistinguishable from a quiet day. (Export errors ARE surfaced — load errors should get the same treatment.) Project-chat sends in app/projects/page.tsx fail equally silently.
app/history/page.tsxapp/projects/page.tsx
Fix effort: Same as History load errors — add error banner instead of empty ledger.
259HistoryPanel load errors render as empty ledger
Data Flows & Wiring
mediumOpenEasy fix
HistoryPanel catches load failures with console.error only; teamActivity stays [] and HistoryLedger shows empty feed. Transient failures inside embedded tool panels are indistinguishable from quiet activity — separate from the full /history page load issue.
Fix effort: Add error state banner in HistoryPanel.tsx like full /history page should.
260HistoryPanel slide-over lacks dialog accessibility semantics
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fix
app/components/HistoryPanel.tsx (~154–161) is a fixed overlay with Escape handler (~126–130) but no role='dialog', aria-modal, or aria-labelledby. Embedded in CindyVue, Shorts and Shanetrée modals — screen-reader users may not perceive it as a modal surface. Other modals' a11y gaps are tracked separately.
app/components/HistoryPanel.tsx
Fix effort: Add role='dialog' aria-modal aria-labelledby to HistoryPanel overlay container.
261IDE file saves have no content size cap
Build, IDE & Custom Tools
mediumOpenEasy fix
PUT app/api/ide/workspaces/[id]/files/route.ts (~139) accepts unbounded content strings. Combined with Monaco onChange calling saveFileContent on every keystroke (app/ide/page.tsx ~500), a large paste can POST megabytes per edit.
app/api/ide/workspaces/app/ide/page.tsx
Fix effort: Cap content length on PUT; debounce Monaco saves (tracked separately).
262IDE saves on every keystroke with no debounce
Performance & Scale
mediumOpenEasy fix
app/ide/page.tsx MonacoEditor onChange calls saveFileContent immediately for every character typed — a PUT per keystroke (optimistic local update + network). Creates API storms, version-contention reloads and unnecessary DB writes during normal typing.
app/ide/page.tsx
Fix effort: 300ms debounce on saveFileContent in ide/page.tsx onChange.
263IDE workspace/file operations fail silently
Data Flows & Wiring
mediumOpenEasy fix
app/ide/page.tsx initial loads use .catch(() => {}). Create/delete file paths do if (!response.ok) return with no user feedback. Failed workspace creation also returns quietly. Operators get no toast or inline error when saves, deletes or loads fail.
app/ide/page.tsx
Fix effort: Toast or inline error on !response.ok paths in ide/page.tsx.
264Inbound kiosk exposes operator and debug schedule UI
UI, Flow & Density
mediumOpenEasy fix
app/inbound/InboundClient.tsx Schedule Outlook section (~704–762) shows upload history (details/summary), PO diff counts (+added / -removed vs previous upload) and refresh/hide controls on the public /inbound kiosk. Floor operators see backend scheduling metadata meant for admins — adds visual noise and leaks operational churn on a scan-first screen.
app/inbound/InboundClient.tsx
Fix effort: Hide upload history, PO diff and refresh behind admin flag or move to /controls; kiosk shows search results only.
265internal shanetree-autopull accepts unbounded pulled_data JSON
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fixMay affect neighbors
app/api/internal/shanetree-autopull/route.ts parses full JSON body (~124) and writes pulled_data to shanetree_snapshots/pull log (~144+) with no payload size cap. Tracked issue covers hardcoded SHANETREE_AUTOPULL_USER_ID; this is separate — compromised INTERNAL_API_SECRET could POST multi-megabyte ODBC payloads into snapshot rows.
app/api/internal/shanetree-autopull/route.ts
Fix risk: Adding caps breaks export or load-all flows that depend on fetching entire tables.
Fix effort: Reject bodies >4MB before snapshot insert — align with relay payload cap comments.
266Invalidating one company's API keys flushes every tenant's
Data Integrity & Races
mediumOpenEasy fixMay affect neighbors
apiKeyCache.invalidateCompany(companyId) in lib/cache/service.ts accepts a companyId and then ignores it — it runs cacheDeletePattern('fd:api_key:*'), evicting all companies' cached key hashes. Revoking one tenant's key causes a global cache stampede of re-auth DB lookups. Relatedly, /api/cache's invalidate_user action accepts any target userId without checking it belongs to the caller's company.
lib/cache/service.ts
Fix risk: Per-tenant cache key is correct fix but changes thundering herd timing — all tenants re-auth on any revoke.
Fix effort: Same scoped cache invalidation.
267Job enqueue has no throttle
Rate Limits & Abuse Surface
mediumOpenEasy fix
/api/jobs/enqueue lets any authenticated session enqueue ai_analysis, batch_email, embedding_generation and data_export jobs with arbitrary payloads, with no rate limit — a single misbehaving client can flood QStash and background_jobs with expensive work while sibling routes are throttled.
Fix effort: Add withRateLimit to /api/jobs/enqueue.
268Legacy team-chat message CRUD is orphaned
Dead Code & Orphan Features
mediumOpenEasy fix
lib/supabase/team-chat.ts still exports sendTeamMessage, getTeamMessages, getRecentTeamMessages and deleteTeamMessage (~175 lines) targeting team_chat_messages, but grep finds zero imports outside that file. Live /team-chat uses lib/supabase/chat-rooms.ts (sendChatRoomMessage, subscribeToChatRoomMessages). Leftover from pre–chat_rooms migration — confuses readers and duplicates mention-insert logic.
lib/supabase/team-chat.tslib/supabase/chat-rooms.ts
Fix effort: Delete sendTeamMessage/getTeamMessages/getRecentTeamMessages/deleteTeamMessage from team-chat.ts or migrate last caller; keep mention helpers.
269lib/supabase/anomalies.ts duplicates live service wrappers
Dead Code & Orphan Features
mediumOpenEasy fixMay affect neighbors
The 271-line client module mirrors lib/anomaly/service.ts, but only detectAndNotifyAnomalies and extractForkliftMetrics are imported (app/forklift/page.tsx). getRecentAnomalies, acknowledgeAnomaly, dismissAnomaly, extractPickerMetrics and extractLoaderMetrics in the client file have zero callers — API routes use service.ts directly. ~185 lines of duplicate dead wrappers.
lib/anomaly/service.tsapp/forklift/page.tsx
Fix risk: Deduping parallel implementations must update every copy or behavior drifts again.
Fix effort: Keep only detectAndNotifyAnomalies + extractForkliftMetrics in client module; delete duplicate wrappers.
270MapPanel caches full customer roster in global localStorage
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fixMay affect neighbors
app/entree/MapPanel.tsx CACHE_KEY = filedisplay:accounts-map:v1 (~49) with no userId or companyId suffix. Shared bridge PCs inherit another operator's cached Entrée accounts roster, geocodes and salesman filters — wrong map pins until manual refresh.
app/entree/MapPanel.tsx
Fix risk: Namespacing keys resets shared-PC preferences until users reconfigure.
Fix effort: Suffix CACHE_KEY with userId or companyId in MapPanel.tsx — match dashboard hidden-cards pattern.
271Meat-count range queries lack row caps on browser loads
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fix
lib/supabase/meat-counts.ts listMeatCountsForRange() (~314–329) has no .limit(); listMeatCountUploads() pulls .limit(5000) rows then reduces client-side (~350–354). Large date ranges can pull entire meat_counts partitions into browser memory on /meat-counts analytics tabs.
lib/supabase/meat-counts.ts
Fix effort: Add .limit() on listMeatCountsForRange; paginate analytics tabs.
272Members load failures masquerade as empty team
Data Flows & Wiring
mediumOpenEasy fix
app/members/page.tsx loadOrganizationMembers and loadLeaderboard catch errors with console.error only. UI renders 'No team members found' / 'No activity data yet' with no distinction from a real empty roster or a transient Supabase failure.
app/members/page.tsx
Fix effort: Distinguish error state from empty roster in members/page.tsx.
273Members quick-action modal is mouse-only
UI, Colors & Aesthetics
mediumOpenEasy fix
app/members/page.tsx member detail overlay is a fixed inset-0 div closed only by backdrop click or X button. No role='dialog', aria-modal, Escape handler or focus trap — keyboard users can't dismiss it or tab through actions safely. Same accessibility gap class as the calendar grid.
app/members/page.tsx
Fix effort: Add Escape handler, focus trap, role=dialog on members overlay.
274Memory-cleanup cron can skip companies with few expired rows
API & Operations
mediumOpenEasy fix
app/api/cron/memory-cleanup/route.ts discovery loop breaks when newCompanyIds.length === 0 even though more expired agent_memory may exist. If one tenant dominates the first 500-row batch, other tenants' expired rows are never discovered that run.
app/api/cron/memory-cleanup/route.ts
Fix effort: Don't break on newCompanyIds.length === 0 until query returns zero rows; or DISTINCT company_id query.
275Nine Vercel crons, two of them sub-5-minute
Dependencies & Build Hygiene
mediumOpenEasy fix
vercel.json schedules job-retries every minute and stale-jobs every two minutes — ~2,900 authenticated invocations a day before the other seven jobs. All routes exist and all check CRON_SECRET, but the density is real function-invocation and Supabase load for queues that are usually empty. Worth stretching the intervals or moving retry logic into the worker itself.
Fix effort: Stretch job-retries and stale-jobs intervals in vercel.json if ops agrees.
276Nine Vercel crons, two sub-5-minute
Code Quality & Hygiene
mediumOpenEasy fix
vercel.json lists 9 crons: job-retries */1 * * * * (~line 4) and stale-jobs */2 * * * * (~line 9) fire every 1–2 minutes; plus agent-autonomy */10, webhooks */5, port-ai-summary hourly, etc. ~2,900 invocations/day from the two fast crons alone. Routes check CRON_SECRET but queues are often empty — real serverless + Supabase load for retry sweeps that could run inside workers or at 15-min intervals.
Fix effort: Stretch job-retries to 5m, stale-jobs to 5m in vercel.json — config-only if ops agrees.
277No CONTRIBUTING.md or CODEOWNERS — contributor and review norms undocumented
Repository Structure Audit (Pass 23)
mediumOpenEasy fix
LICENSE.md and AGENTS.md exist (Cursor Cloud oriented) but no human CONTRIBUTING.md (branch naming, commit style, moneyfund push remote, problems-page workflow). No CODEOWNERS for app/api/, supabase/migrations/, or entree-bridge.js. External collaborators or future hires lack a single 'how we work' entry point beyond scattered .cursorrules.
app/api/supabase/migrations/
Fix effort: Add CONTRIBUTING.md from .cursorrules + commit style; CODEOWNERS for migrations and api/.
278No security headers anywhere
Security & Trust Boundaries
mediumOpenEasy fix
There's no middleware.ts and next.config.ts defines no headers() — the app ships without CSP, HSTS, X-Frame-Options, X-Content-Type-Options or Referrer-Policy. For a multi-tenant SaaS with admin routes and public kiosk pages, a baseline headers() block in next.config is cheap insurance against clickjacking and MIME sniffing.
Fix effort: Same next.config.ts headers block.
279No security headers configured
Security & Trust Boundaries
mediumOpenEasy fix
No middleware.ts and next.config.ts defines no headers() — the app ships without CSP, HSTS, X-Frame-Options, X-Content-Type-Options or Referrer-Policy. Baseline headers() in next.config is cheap insurance for a multi-tenant SaaS with admin routes and public kiosk pages.
Fix effort: Add headers() block in next.config.ts (CSP may need iteration) — ~20 lines, test on Vercel preview.
280notification_settings updates return success when the table is missing
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fix
lib/supabase/notifications.ts updateNotificationSetting() (~181–183) and updateNotificationSettings() (~217–220) return { success: true } when the relation does not exist. Account email toggles look saved on fresh/partial DBs but persist nothing — operators think notifications are configured when the table was never migrated.
lib/supabase/notifications.ts
Fix effort: Return success:false when relation missing; surface 'notifications not configured' in account UI.
281OpenAPI spec omits live Calendar endpoints
Agent, API & Docs Integrity
mediumOpenEasy fix
/api/v1/calendar/events and /api/v1/calendar/available-slots work with API keys, but grep 'calendar' in app/api/v1/openapi.json/route.ts returns zero matches — no Calendar tag or paths. Developer docs treat OpenAPI as the v1 contract; calendar is invisible there (batch blocking is tracked separately in API & Operations).
app/api/v1/openapi.json/route.ts
Fix effort: Add /api/v1/calendar/events and available-slots paths to openapi.json route.
282Picker header shows four icon-only toggles on mobile
UI, Flow & Density
mediumOpenEasy fix
app/picker/page.tsx header packs AI, Analytics, Filters and Test toggles with labels hidden sm:inline (~1377–1438). On warehouse phones operators see four identical-looking icon buttons with no text — easy to tap the wrong mode (e.g. Test vs Filters).
app/picker/page.tsx
Fix effort: Show abbreviated labels on xs, tooltips with aria-label, or collapse secondary toggles into one menu.
283port_summary_memory expires_at entries are never pruned
Backend & Database
mediumOpenEasy fix
supabase/migrations/121_create_port_summary_memory.sql adds expires_at with a comment that a prune task is coming, but daily memory-cleanup cron only calls pruneExpiredMemory() on agent_memory. port_summary_memory rows with past expires_at accumulate and keep feeding /port AI context.
supabase/migrations/121_create_port_summary_memory.sql
Fix effort: Extend memory-cleanup cron or add pruneExpiredPortSummary() for port_summary_memory table.
284POST /api/jobs lets any company member cancel or retry jobs
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fix
app/api/jobs/route.ts POST (~148–192) verifies session + company_id ownership but has no role gate — viewers can cancel/retry background jobs. No withRateLimit on cancel/retry path. Expensive ai_analysis or export jobs can be disrupted or re-queued by any authenticated seat.
app/api/jobs/route.ts
Fix effort: Require editor+ role for cancel/retry; add withRateLimit on POST.
285Presence display names never update after async profile load
Data Flows & Wiring
mediumOpenEasy fix
lib/realtime/useCompanyPresence.ts loads full_name async (~43–61) into userName state, but presence channel join effect deps are [companyId, userId] only (~77) — initial track uses userName || 'User' at ~85 before profile resolves. Auto-update effect (~153–157) syncs page/resourceId/mode but not userName. updatePresence callback deps [userId] only (~149). Collaborators who connect before profile fetch completes stay labeled 'User' for the session.
lib/realtime/useCompanyPresence.ts
Fix effort: Add userName to useCompanyPresence effect deps or re-trackPresence after profile loads.
286project_updates email toggle is a no-op
Permissions, Settings & Misleading UI
mediumOpenEasy fixHigh blast radius
Account exposes project_updates (app/account/page.tsx ~67). notifyProjectUpdate() exists in lib/notifications/trigger.ts but grep finds zero callers outside its definition — no project status change anywhere triggers it. Third decorative notification category beside the tracked chat/mention gaps.
app/account/page.tsxlib/notifications/trigger.ts
Fix risk: Wiring latent toggles suddenly enables or blocks behavior users assumed was active.
Fix effort: Call notifyProjectUpdate on status change in projects flow, or hide toggle.
287Projects page loads without a session gate
Auth & Session
mediumOpenEasy fix
app/projects/page.tsx useEffect calls loadQuoteSubmissions() on mount with no auth check. getUserQuoteSubmissions() throws 'You must be logged in' which is caught and console.error'd — logged-out visitors see an empty projects list with no redirect. Auth is only checked inside sendProjectChatMessage().
app/projects/page.tsx
Fix effort: Redirect to /login when getUserQuoteSubmissions throws or on mount session check.
288Public GET leaks every invoice printed from /sales
Window, Sales & Kiosk Scheduling
mediumOpenEasy fix
GET /api/public/will-call-print-request with no id param (route.ts ~138–165) returns up to 5000 invno values where requester='sales' and status IN (pending, printing, done) — unauthenticated. Reveals operational print history to anyone on the internet; complements tracked print-once UX bug with a separate data-disclosure angle.
Fix effort: Require kiosk token or remove unscoped GET; scope printed list to authenticated staff.
289Public kiosk localStorage keys are not namespaced per session
Permissions, Settings & Misleading UI
mediumOpenEasy fixCritical — test everything
Beyond homepage-bg-theme and greco guard cache (tracked elsewhere): /window persists window-my-requests + window-theme (WindowClient.tsx ~35–36), /sales uses sales-theme (~54), /timer uses timer-theme (~27). Shared dock PCs inherit prior visitor appointment lists and dark/light preferences — no user or company suffix on keys.
Fix risk: Locking down public kiosk APIs without token replacement breaks wall-mounted workflows.
Fix effort: Suffix window/sales/timer theme and request keys with session id or kiosk device label.
290Public shanetree-latest exposes operator identity in snapshot payloads
Shanetrée, Routing & Driver Comms
mediumOpenEasy fix
app/api/public/shanetree-latest/route.ts selects user_id on every row (~81–83) and returns the full snapshot including payload. useShanetreeSnapshot.ts embeds user_email and user_id inside payload.__meta (~60–61, 170–172). Kiosk readers on /adrian, /noe and /inbound therefore see who captured routes, will-calls or schedule data — PII on a world-readable endpoint.
app/api/public/shanetree-latest/route.ts
Fix effort: Strip user_id and payload.__meta from public response; keep full row on authenticated snapshot API.
291Public tool share URLs (/t/{share_id}) have no in-app surfacing
Permissions, Settings & Misleading UI
mediumOpenEasy fix
v1 tools/generate returns share_url at /t/{share_id} and app/t/[id]/page.tsx renders public tools, but build/page.tsx links to /custom-tools/{id} (auth required) and never shows 'copy public link.' Public-share infrastructure works; product UI never exposes it to builders.
app/t/
Fix effort: Show copy-share-link on build/custom-tools success; surface share_url from v1 generate.
292Public window API accepts past dates and off-grid times
Window, Sales & Kiosk Scheduling
mediumOpenEasy fix
POST /api/public/window-appointments validates DATE_RE and TIME_RE only (~216–220) — no comparison to today and no whitelist against buildTimeSlots() in lib/window/constants.ts (~101–108). Client uses min={localTodayStr()} (WindowClient.tsx ~362) but direct API calls can book 2020-01-01 at 03:17 outside the 7:00–16:30 grid the UI shows.
lib/window/constants.ts
Fix effort: Reject date < today and time not in buildTimeSlots() whitelist server-side.
293Quote uploads have no file-size cap before base64 JSONB insert
Performance & Scale
mediumOpenEasy fix
app/quote/page.tsx accepts arbitrary file count/size via <input type='file' multiple /> with no size check. lib/supabase/quote-submissions.ts encodes every file to data_base64 in quote_submissions.file_attachments JSONB with no server guard — large spreadsheets can blow request size and Postgres row weight.
app/quote/page.tsxlib/supabase/quote-submissions.ts
Fix effort: Client max size check + server rejection in quote-submissions.ts (e.g. 5MB/file).
294Reports tab footer crowds actions on narrow screens
UI, Flow & Density
mediumOpenEasy fix
Each report card footer (~2577–2633) packs timestamp, 'include input file?' checkbox, settings gear and Send Email button into one flex justify-between row. On mobile the checkbox label (text-[9px]) and Send Email button compete for width — actions wrap awkwardly or truncate beside the timestamp instead of stacking.
Fix effort: Stack footer on sm breakpoint — timestamp row, then checkbox, then action buttons; or move include-input into settings modal.
295Reset-password accepts any active session, not just recovery
Auth & Session
mediumOpenEasy fix
app/reset-password/page.tsx onAuthStateChange sets sessionReady on PASSWORD_RECOVERY or SIGNED_IN, and getSession() also sets sessionReady for any existing session (lines 36–39). A normally logged-in user visiting /reset-password can set a new password with no old-password confirmation — blurs recovery vs in-session password change.
app/reset-password/page.tsx
Fix effort: Only set sessionReady on PASSWORD_RECOVERY event, not generic SIGNED_IN/getSession.
296Routing APIs accept any authenticated tenant, not Greco-only
Shanetrée, Routing & Driver Comms
mediumOpenEasy fixHigh blast radius
/api/routing/routes, /api/routing/routes/[id] and /api/routing/ai-build authenticate any user with profiles.company_id (routes/route.ts ~12–33) — no NEXT_PUBLIC_GRECO_COMPANY_ID pin unlike driver-chat requireGrecoUser(). On a multi-tenant deploy, non-Greco companies can store routes in greco_routes and invoke routing AI. Concrete unguarded surface beyond the broad Greco-hardcode note in older audit sections.
Fix risk: Pinning to Greco UUID breaks routing tool for any future non-Greco tenant already using /routing.
Fix effort: Pin routing routes to GRECO_COMPANY_ID like driver-chat requireGrecoUser.
297Routing planner defaults new routes to UTC today
Shanetrée, Routing & Driver Comms
mediumOpenEasy fixHigh blast radius
freshRoute() in app/routing/RoutingClient.tsx (~96) sets date: new Date().toISOString().split('T')[0]. Eastern evening dispatchers opening a blank route get tomorrow's route_date before touching the picker — same warehouse-clock skew as Entree todayStr() (pass 10) but /routing is a separate daily workflow.
app/routing/RoutingClient.tsx
Fix risk: Date boundary changes reorder stats, leaderboards, and filters keyed on existing date columns.
Fix effort: Use shared warehouseLocalDateStr() in freshRoute() like other Greco date fixes.
298sales-relay POST creates a new relay row on every call
Window, Sales & Kiosk Scheduling
mediumOpenEasy fix
app/api/public/sales-relay/route.ts (~116–134) inserts into shanetree_relay_requests with no per-date/invno dedupe or cooldown. Day-switching, refresh loops or multiple /sales kiosks can stack duplicate /will-calls jobs for the same date. Claim RPC global scope is tracked separately — this is enqueue-side duplication.
app/api/public/sales-relay/route.ts
Fix effort: Dedupe pending jobs for same endpoint+date/invno before insert.
299scripts/fake-lockscreen and scripts/popup-killer are kiosk hacks inside the SaaS repo
Repository Structure Audit (Pass 23)
mediumOpenEasy fix
git tracks scripts/fake-lockscreen/ (PowerShell + VBS lock-screen overlay) and scripts/popup-killer/ (auto-close print dialogs) — 8 files with autostart installers. Legitimate for a dedicated bridge PC playbook, but jarring in a multi-tenant SaaS repository a customer or auditor might clone. Relocate to bridge-deploy/ or a private ops repo; keep only scripts/refresh-platform-stats.mjs and migration helpers in the product tree.
Fix effort: Move to bridge-deploy/ or private ops repo; remove from main product tree.
300Seven operator/setup markdown files sit at repo root without a docs/ folder
Repository Structure Audit (Pass 23)
mediumOpenEasy fix
AGENTS.md, MULTI_TENANCY_SETUP.md, SHANETREE_REMOTE_SETUP.md, TOOL_REPORTS_SETUP.md, COMPANY_ID_VS_EMAIL_DOMAIN.md, QUICK_START.md and LICENSE.md share root with package.json. No docs/ index, no mkdocs/vitepress site, no cross-links. Professional OSS/SaaS repos use docs/ or a single DOCUMENTATION.md hub — root looks cluttered to GitHub visitors and Vercel importers.
Fix effort: Create docs/; move setup markdowns; add docs/README.md hub linking AGENTS + QUICK_START.
301Shanetrée autopull logs pull success before snapshot insert completes
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenEasy fix
app/api/internal/shanetree-autopull/route.ts (~177–190) inserts status:success into shanetree_pull_log before shanetree_snapshots insert (~222+). On snapshot failure it writes error log (~242–256) but leaves the earlier success pull row — Pull Log shows completed pull with no matching snapshot for operators debugging auto-pull.
app/api/internal/shanetree-autopull/route.ts
Fix effort: Insert pull_log only after snapshot succeeds, or update status to failed on snapshot error.
302Shanetrée modal permissions fail open on query errors
Shanetrée, Routing & Driver Comms
mediumOpenEasy fixHigh blast radius
getMyDeniedModals() in lib/supabase/shanetree-modal-permissions.ts (~92–96) returns an empty Set (full modal access) when the Supabase read fails — mirror of greco_card_permissions fail-open. A transient RLS/network error unlocks modals an admin denied in /controls until the query succeeds.
lib/supabase/shanetree-modal-permissions.ts
Fix risk: Fail-closed change denies access on transient errors — opposite of today's permissive behavior.
Fix effort: Fail closed to last-known denials in sessionStorage or block Entree until permissions load.
303shanetree_pull_log / shanetree_snapshots lack composite indexes
Backend & Database
mediumOpenEasy fix
lib/supabase/shanetree-pull-log.ts and shanetree-snapshots.ts order by created_at DESC scoped to a company. Migrations 116 and 118 index company_id and created_at separately, not as (company_id, created_at). Postgres must combine two indexes or sort a larger slice as pull-log and snapshot volume grows.
lib/supabase/shanetree-pull-log.ts
Fix effort: Migration: CREATE INDEX ON (company_id, created_at DESC) for both tables.
304Six ai-conversations exports beyond subscribeToAIConversationComments are orphaned
Dead Code Hunt (Pass 22)
mediumOpenEasy fix
lib/supabase/ai-conversations.ts exports logAIConversation() (~35), getAIConversationsWithComments() (~128), getAIConversationsByUser() (~178), addConversationComment() (~204), deleteConversationComment() (~258) and getConversationComments() (~282) — all with zero external callers. Dashboard uses getAIConversations + subscribeToAIConversations only; comment CRUD and enriched list queries were built but never surfaced (~250 lines). subscribeToAIConversationComments orphan is tracked separately in Hygiene.
lib/supabase/ai-conversations.ts
Fix effort: Delete comment CRUD exports or add dashboard conversation thread UI.
305Team-chat create modal lacks dialog accessibility
UI, Colors & Aesthetics
mediumOpenEasy fix
app/team-chat/page.tsx New Chat overlay is a fixed inset-0 div with no role='dialog', aria-modal, Escape-to-close or focus trap. Keyboard users cannot dismiss it safely or tab through member selection — same class as the Members modal gap.
app/team-chat/page.tsx
Fix effort: Add role=dialog, aria-modal, Escape handler and focus trap to team-chat New Chat overlay.
306Thruput never logs to the analytics log or sends report notifications
Greco Warehouse Operations
mediumOpenEasy fix
grep logReportGeneration and notifyReportGenerated under app/thruput/ returns zero matches. Every other major warehouse report tool emits team_activity_log rows and optional email/webhook notifications on save. Thruput night-shift data stays invisible to Daily Stats Analytics Log, leaderboard scoring and notifyReportGenerated subscribers.
app/thruput/
Fix effort: Call logReportGeneration + notifyReportGenerated on saveThruputDay like other warehouse tools.
307ToolAPI bridge reports success when cloud save failed
Data Flows & Wiring
mediumOpenEasy fix
lib/custom-tools/bridge.ts responds { success: true, fallback: true } to the iframe when /api/custom-tools/data returns an error or throws (lines 648–656). Custom tools and Build preview show success while data exists only in browser localStorage — silent data loss on shared machines or after cache clear.
lib/custom-tools/bridge.ts
Fix effort: Return success: false to iframe on API failure; let tool show error instead of silent localStorage fallback.
308ToolAPI cloud-save fallback localStorage is not namespaced per user
Build, IDE & Custom Tools
mediumOpenEasy fixMay affect neighbors
lib/custom-tools/bridge.ts (~434–435) uses fd_toolapi_fallback_${toolId} when cloud save fails. Tracked preview keys (fd_build_preview_tool_data_v1) are separate — this persisted-fallback path bleeds across operators on shared warehouse PCs.
lib/custom-tools/bridge.ts
Fix risk: Cloud-save failure path bleeds tool state across logins — fix changes offline fallback semantics.
Fix effort: Prefix fd_toolapi_fallback keys with userId like preview storage fix.
309triggerItemReceivedAlert is never wired — only checked-in stage fires
Dead Code Hunt (Pass 22)
mediumOpenEasy fix
lib/inbound/item-alerts.ts:172 exports triggerItemReceivedAlert() for the 'item received' email stage. app/api/dockvoo/notify/route.ts imports and calls triggerItemCheckedInAlert only (~148). The received-stage alert path and its email template wiring are dead — operators never get the intermediate notification even if item_received template exists in EmailService.
lib/inbound/item-alerts.tsapp/api/dockvoo/notify/route.ts
Fix effort: Call triggerItemReceivedAlert from dockvoo notify earlier stage or remove item_received template path.
310v1 batch executes every operation in parallel via Promise.all
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fixMay affect neighbors
app/api/v1/batch/route.ts (~136–179) runs all operations concurrently with no concurrency cap. Large batches (reports + search + files) can hammer rate limits, Postgres and downstream APIs simultaneously — thundering herd from a single API key.
app/api/v1/batch/route.ts
Fix risk: Serializing ops changes latency profile; rate limits may trip differently for batch clients.
Fix effort: Add concurrency limit (e.g. p-limit 3) over batch operations — batch/route.ts.
311v1 POST /filters accepts unbounded filter_prompt strings
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fixMay affect neighbors
app/api/v1/filters/route.ts requires filter_prompt as a string (~78–80) but enforces no max length before upserting into ai_report_filters (~95–107). API keys can store multi-megabyte prompts later injected into AI system instructions (including via v1 analyze).
app/api/v1/filters/route.ts
Fix risk: Adding caps breaks export or load-all flows that depend on fetching entire tables.
Fix effort: Cap filter_prompt length (e.g. 8KB) — match ai_report_filters UI limits.
312v1 POST /reports/analyze stores analyses under a nil system user UUID
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fix
app/api/v1/reports/analyze/route.ts inserts user_id: '00000000-0000-0000-0000-000000000000' (~229) into tool_ai_analyses. Activity ledger, analytics attribution and per-user analysis history cannot distinguish which API key or operator triggered the run.
app/api/v1/reports/analyze/route.ts
Fix effort: Store api_key.created_by or a dedicated api_service user per company — migration optional for history.
313v1 POST /search returns full embedding content_text bodies
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenEasy fix
app/api/v1/search/route.ts maps every result field including content_text (~76). Semantic search can return large report/analysis text blobs to any API key with semantic_search + reports:read — bulk export surface beyond summaries, including PII from embedded report JSON.
app/api/v1/search/route.ts
Fix effort: Return content_summary only by default; opt-in include_full_text flag for API consumers who need it.
314v1 schedules PUT never recalculates next_run_at after edits
Email, v1 API & Entrée Panel Polish
mediumOpenEasy fix
app/api/v1/schedules/[id]/route.ts (~103–117) updates cron/timezone/interval but leaves next_run_at stale. Operators editing a schedule's cron via API do not shift the next fire time — combined with write-only schedules table, edits look saved but timing semantics are broken.
app/api/v1/schedules/
Fix effort: Recompute next_run_at on PUT when cron/timezone/interval change — schedules/[id]/route.ts.
315v1 tools API stores created_by as the API key UUID
Build, IDE & Custom Tools
mediumOpenEasy fix
app/api/v1/tools/route.ts (~104) and v1 tools/[id]/data write created_by:apiKey.id, not the key creator's user UUID. custom_tools.created_by references auth.users(id) per migration 075 — corrupts attribution and may violate FK unless keys happen to match user ids.
app/api/v1/tools/route.ts
Fix effort: Store apiKey.userId or key creator profile id instead of apiKey.id.
316Webhook cron lock table does not exist — locking is fail-open
API & Operations
mediumOpenEasy fixHigh blast radius
app/api/cron/webhooks/route.ts acquireCronLock upserts into cron_locks, but no migration creates that table (grep cron_locks in supabase/migrations returns zero). On error it returns true ('skip locking and proceed anyway'). With webhooks scheduled every 5 minutes, overlapping retry runs are possible.
app/api/cron/webhooks/route.tssupabase/migrations
Fix risk: Fail-closed change denies access on transient errors — opposite of today's permissive behavior.
Fix effort: Migration creating cron_locks table + keep fail-closed on lock miss if desired.
317x-api-user-id is spoofable on custom-tools/generate
Build, IDE & Custom Tools
mediumOpenEasy fix
app/api/custom-tools/generate/route.ts (~416–428): when a valid API key matches x-api-company-id, x-api-user-id is trusted for attribution and downstream inserts without verifying the UUID belongs to the key creator. API consumers can attribute AI usage to arbitrary user IDs within the tenant.
app/api/custom-tools/generate/route.ts
Fix effort: Bind user id to API key creator profile; ignore client x-api-user-id except internal secret path.
318/quotes-admin is tenant-scoped despite admin chrome
Controls, Admin & Collaboration
mediumOpenModerate effort
loadSubmissions in app/quotes-admin/page.tsx selects all rows but RLS (migration 012 SELECT policy) limits to caller's company_id. Password gate + 'admin' UI imply platform-wide quote review; data is single-tenant unless the operator belongs to each company.
app/quotes-admin/page.tsx
Fix effort: Use service-role /api/admin/submissions for list or relabel UI as company admin.
319/scan re-implements the tool detector
Code Quality & Hygiene
mediumOpenModerate effort
app/scan/page.tsx (~103–273) implements its own columnMatches(), required/unique/exclude scoring, and 0.7 auto-route threshold inline. lib/tools/detector.ts exports detectTool(), detectAllTools(), calculateMatchScore() with the same 0.7 threshold (~72) and TOOL_SIGNATURES from lib/tools/registry.ts. Scan page never imports detector.ts — two implementations that drift when column aliases change in processors.
app/scan/page.tsxlib/tools/detector.tslib/tools/registry.ts
Fix effort: Replace scan/page.tsx logic with lib/tools/detector.ts import — verify parity on test files.
320/window and /windowadmin are English-only
Window, Sales & Kiosk Scheduling
mediumOpenModerate effort
app/window/WindowClient.tsx and app/windowadmin/WindowAdminClient.tsx have zero useLanguage imports. /sales uses bilingual t(en, es) throughout (SalesClient.tsx). Spanish-preference warehouse users get English scheduling and staff review UIs on the will-call booking path.
app/window/WindowClient.tsxapp/windowadmin/WindowAdminClient.tsx
Fix effort: Wrap WindowClient and WindowAdminClient copy with useLanguage like SalesClient.
321/windowadmin Will Calls tab has no date picker — latest snapshot only
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenModerate effort
WindowAdminClient.tsx WillCallsPanel (~340–361) fetches only GET /api/public/shanetree-latest?modal=willCalls once on mount. Staff reviewing appointment requests cannot see will-call context for prior/future days without leaving admin for /sales or Shanetrée — limits operational review of booking backlog.
Fix effort: Add date picker + shanetree_snapshots or will_call_appointments query by date — WindowAdminClient.tsx.
32218 Windows executables and launchers are tracked without an ops/ namespace
Repository Structure Audit (Pass 23)
mediumOpenModerate effort
git ls-files lists 18 paths ending in .exe, .bat, .vbs, or .ps1 — SumatraPDF.exe, start-shanetree-autoprint.bat, scripts/*.ps1, scripts/start-entree-bridge-hidden.vbs, heal-and-restore.ps1, etc. No CONTRIBUTING.md explains which are dev-only. Professional layout: ops/windows/ or bridge/ with a single SETUP-BRIDGE.md index linking install-bridge-autostart.ps1 and verify-architecture.ps1.
Fix effort: Consolidate under ops/windows/ with SETUP-BRIDGE.md index; gitignore or LFS for any remaining binaries.
32379 flat route folders under app/ with no domain grouping
Repository Structure Audit (Pass 23)
mediumOpenModerate effort
ls app/ shows 79 top-level entries — forklift, picker, entree, timer, sales, controls, problems, phl, produce, etc. all siblings. No app/(warehouse)/, app/(kiosk)/, app/(admin)/ or app/(platform)/ route groups. Next.js supports parentheses groups without changing URLs; grouping would document IA, enable shared layouts per surface, and stop the app/ directory reading like an uncurated dump of every URL ever shipped.
Fix effort: Introduce app/(warehouse), (kiosk), (platform) route groups without URL changes — incremental moves.
324AgentGenerativeUI eagerly pulls eight @nivo packages into /chat
Monolith Files & Bundle Weight
mediumOpenModerate effort
app/components/AgentGenerativeUI.tsx (2,047 lines) static-imports recharts plus ResponsiveSankey, Chord, Bump, Stream, SwarmPlot, CirclePacking, Network, Sunburst, ParallelCoordinates, and Marimekko from @nivo/* (~13–23). app/chat/page.tsx and daily-stats/GrecoAnalyticsChat.tsx import it at module top — opening Team Chat downloads the full nivo suite even when the conversation has no generative UI widgets. Lazy-load per chart type from spec.kind.
app/components/AgentGenerativeUI.tsxapp/chat/page.tsx
Fix effort: Switch on spec.kind with dynamic(() => import('@nivo/sankey')) per chart type — reduces /chat first load; test all generative UI widget kinds.
325Aggressive polling loops
Scalability & Performance
mediumOpenModerate effortHigh blast radius
app/entree/EntreeClient.tsx will-call/auto-print setInterval(..., 3_000) at ~27220 and ~27363. app/live/greco/page.tsx (~128–129) reloads full session every 10_000 ms without document.hidden check. Some EntreeClient heartbeat loops (~10787, ~11296) skip hidden tabs; not universal. dashboard 30s refresh and inbox check visibility — kiosk paths do not. Dock screens open all shift → sustained load for rarely-changing data.
app/entree/EntreeClient.tsxapp/live/greco/page.tsx
Fix risk: Duplicate — EntreeClient and /live/greco polling is load-bearing for will-call print and session sync.
Fix effort: Same polling → Realtime/backoff pass.
326Aggressive polling loops on kiosk screens
Performance & Scale
mediumOpenModerate effortHigh blast radius
app/entree/EntreeClient.tsx will-call claim and auto-print watchers use setInterval(..., 3_000) at ~27220 and ~27363 — some ticks skip when document.hidden, not all loops. /live/greco/page.tsx (~128–129) reloads full DockSheet session every 10_000 ms with no visibility gate. Shanetrée heartbeat loops use 5 min / 30 min intervals (~10787, ~11296) with hidden-tab skips on some paths only. Multiply by dock kiosks left open all shift — sustained Postgres reads for rarely-changing data.
app/entree/EntreeClient.tsx
Fix risk: Stretching intervals makes wall mounts show stale dock/session data — operators rely on 3–10s refresh during receiving.
Fix effort: Replace 3s/10s polls with Realtime or Page Visibility backoff — touches Shanetrée, live/greco, will-call loops.
327ai_analysis job emailRecipients is an authenticated email relay via the queue
Security & API Gaps (Pass 24)
mediumOpenModerate effortHigh blast radius
app/api/jobs/workers/ai-analysis/route.ts (~179–196): when payload.sendEmailOnComplete and payload.emailRecipients are set, worker enqueues batch_email to arbitrary addresses under FileDisplay's Resend domain. app/api/jobs/enqueue/route.ts (~141–145) spreads user payload into enrichedPayload with no recipient allowlist. Any authenticated user can enqueue ai_analysis (ALLOWED_JOB_TYPES) with external emails — parallel open relay to tracked /api/email/send gap.
app/api/jobs/workers/ai-analysis/route.tsapp/api/jobs/enqueue/route.ts
Fix risk: Allowlist must include legitimate batch_email paths from picker/forklift notify chains, not only company members.
Fix effort: Validate emailRecipients against company member emails in enqueue worker; reject external domains.
328ai-usage-rollup cron loads entire month of ai_usage_logs into Node memory
Security & API Gaps (Pass 24)
mediumOpenModerate effort
app/api/cron/ai-usage-rollup/route.ts (~26–29) selects all ai_usage_logs rows gte monthStart with no .limit() — entire current month across all tenants into monthLogs array. Second query (~92–96) pulls 8-day window for anomaly detection, also unbounded. Client-side for loops aggregate in Node. Busy month + many tenants → 1 GB Vercel memory exhaustion, stale rollups. Distinct from tracked upsert-failure gap — this is read-side scale.
app/api/cron/ai-usage-rollup/route.ts
Fix effort: Replace client-side loops with Postgres RPC GROUP BY company_id or chunked .range() pagination.
329Anomaly baselines lose samples under concurrent processing
Data Integrity & Races
mediumOpenModerate effort
updateBaseline() in lib/anomaly/service.ts SELECTs the employee_baselines row, appends to recent_values and recomputes stats in JS, then upserts the whole JSONB back. Two reports processing in parallel for the same employee/metric silently drop one sample (last writer wins), quietly skewing the mean and std-dev the detector compares against. Needs a row lock, version column, or SQL-side aggregation.
lib/anomaly/service.ts
Fix effort: Same concurrent baseline fix.
330Anomaly baselines lose samples under concurrent updates
Backend & Database
mediumOpenModerate effortMay affect neighbors
lib/anomaly/service.ts updateBaseline() (~162–191): SELECT employee_baselines row, push newValue onto recent_values in JS, recompute day_of_week_stats, upsert whole JSONB. Parallel report processing for same employee/metric/tool is last-writer-wins — one sample silently dropped, mean/std-dev skew. No row lock, version column, or SQL-side jsonb_set append.
lib/anomaly/service.ts
Fix risk: Row locks during forklift save burst may slow report processing — trade throughput for accuracy.
Fix effort: SQL-side jsonb append RPC with row lock, or version column — lib/anomaly/service.ts refactor.
331Anomaly detectAnomalies does N+1 DB round-trips per employee metric
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenModerate effort
lib/anomaly/service.ts detectAnomalies() (~251–268) loops metrics sequentially, calling getBaseline() then updateBaseline() (each upserts employee_baselines) per employee/metric. A forklift report with 30 employees × 5 metrics can issue ~300 sequential queries on save — tracked baseline race is separate; this is per-save latency and connection churn.
lib/anomaly/service.ts
Fix effort: Batch baseline fetch/upsert in one RPC or Promise.all with concurrency cap — lib/anomaly/service.ts.
332app/problems/page.tsx is a 4,800-line meta-file — split before pass 24
Repository Structure Audit (Pass 23)
mediumOpenModerate effort
The /problems audit itself lives in one page.tsx (NEW_CATEGORIES + CATEGORIES + tab UI). needs.tsx was extracted for Biggest Needs; ranked.ts, taxonomy.ts, fix-metadata.ts exist — but 22 audit passes still append to page.tsx. Next structural step: move NEW_CATEGORIES slices to app/problems/passes/*.ts or one file per pass. Prevents the documentation system from becoming the next EntreeClient-scale monolith.
app/problems/passes/
Fix effort: Extract NEW_CATEGORIES to app/problems/passes/*.ts like needs.tsx pattern.
333Archived activity log is invisible to authenticated users
Backend & Database
mediumOpenModerate effort
supabase/migrations/089_team_activity_log_archive.sql enables RLS on team_activity_log_archive with zero SELECT policies (service-role only). After activity-log-cleanup cron moves rows, they vanish from /history and HistoryPanel with no UI to query the archive.
supabase/migrations/089_team_activity_log_archive.sql
Fix effort: Add SELECT RLS on archive matching company_id + optional /history?archive= UI.
334Build file profiler scans every spreadsheet row client-side
Build, IDE & Custom Tools
mediumOpenModerate effortHigh blast radius
lib/custom-tools/file-profiler.ts (~124–145) iterates all data rows per sheet. Build file mode (app/build/page.tsx ~631) runs this in-browser before generate-from-file — large uploads can freeze the tab before the API is hit.
lib/custom-tools/file-profiler.tsapp/build/page.tsx
Fix risk: Spreadsheet parser changes affect every upload tool — cell types and dates may shift.
Fix effort: Sample rows cap in file-profiler.ts; stream profile on server instead.
335Calendar has two parallel event-detail interaction models
UI, Flow & Density
mediumOpenModerate effort
Grid view supports (a) clicking a day → bottom/side day panel with inline EventDetail cards (~806–860) and (b) clicking an event chip → expandedEventId fixed overlay (~864–878). Two different UIs for the same action; day panel and chip overlay can both be open conceptually but overlay only shows when !selectedDay. Confusing which click path to use.
Fix effort: Pick one path — day panel OR chip overlay — and remove the other; or unify into a single EventDetail drawer.
336Calendar is mouse-only
UI, Colors & Aesthetics
mediumOpenModerate effort
app/calendar/page.tsx grid day cells (~712–714) are <div onClick={() => setSelectedDay(day)}> with no role='button', tabIndex, or onKeyDown. EventFormModal (~188) is fixed inset-0 overlay closed by backdrop click only — no role='dialog', aria-modal, or Escape handler. Keyboard users cannot select days or dismiss the create/edit modal via standard patterns.
app/calendar/page.tsx
Fix effort: Add role=button, tabIndex, onKeyDown, focus trap + Escape on event modal — calendar/page.tsx.
337canAccessTool() is dead code — legacy tool ACL never runs
Controls, Admin & Collaboration
mediumOpenModerate effort
lib/supabase/member-permissions.ts exports canAccessTool() (~304) with zero imports outside the module. Legacy tool_access rows are not consulted on /forklift, /picker or other routes — only dashboard card filtering via greco_card_permissions (and even that is cosmetic per pass 10). Member Permissions 'tool access' toggles may be decorative for direct navigation.
lib/supabase/member-permissions.ts
Fix effort: Call canAccessTool in GrecoOnlyGuard or delete member_permissions.tool_access if greco_card_permissions is canonical.
338Controls has no email or notification settings section
Controls, Admin & Collaboration
mediumOpenModerate effort
app/controls/ControlsClient.tsx sections (~45–52): cards, tools, modals, team, appearance, tabs, permissions — no email/notification matrix. Per-tool email recipient admin lives only in the unwired dashboard modal and Reports-tab gear icons. Controls subtitle promises 'everything for your Client ID' but omits the email surface founders configure via member permission flags.
app/controls/ControlsClient.tsx
Fix effort: Add Email section to Controls wiring report_email_settings + AI filters, or wire dashboard modal.
339Controls Window Access does not gate the public /window scheduler
Window, Sales & Kiosk Scheduling
mediumOpenModerate effortHigh blast radius
ModalAccessSection title 'Window Access' (app/controls/sections/ModalAccessSection.tsx) drives shanetree_modal_permissions for Entrée modal keys only. Public /window posts to /api/public/window-appointments with no modal-permission check. Admins think they restricted 'Window' in Controls; customers can still self-book at /window.
app/controls/sections/ModalAccessSection.tsx
Fix risk: Gating /window breaks public Will-Call kiosk URLs printed on QR collateral unless redirect target is preserved.
Fix effort: Either rename Controls section or add server flag to block window-appointments POST when disabled.
340Counting eagerly bundles framer-motion and recharts on first paint
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenModerate effort
app/counting/CountingClient.tsx statically imports framer-motion (~39) and six recharts components (~45–54). Opening /counting downloads animation + chart libraries before any chart renders — not covered by tracked picker/shorts recharts notes or ExcelJS CDN list.
app/counting/CountingClient.tsx
Fix effort: dynamic() CountingCharts child with lazy recharts; reduce framer-motion to CSS transitions where possible.
341DockSheet Live typography is too small for wall-mounted reading
UI, Flow & Density
mediumOpenModerate effort
app/live/greco/page.tsx relies heavily on text-[9px]–text-[11px] for status badges, PO numbers, building/layout chips and table cells (~161–643); compact mode goes down to text-[7px]–text-[8px]. Designed for dense data on a monitor, but wall-mounted kiosk TVs at 10–15 feet make PO numbers and status labels hard to read at a glance.
app/live/greco/page.tsx
Fix effort: Add kiosk/wall mode with scaled type (text-sm+), larger PO badges and optional TV layout preset.
342Document notification route bypasses NotificationService and never writes email_logs
Email, v1 API & Entrée Panel Polish
mediumOpenModerate effort
app/api/notifications/document/route.ts POSTs directly to Resend (~154–166). Failed-send counting on document route may be tracked separately — this angle is the missing audit trail: no email_logs rows for team document-share notifications, unlike session email paths through NotificationService.
app/api/notifications/document/route.ts
Fix effort: Route document POST through NotificationService.send or insert email_logs rows after Resend — notifications/document/route.ts.
343Documents broadcast content_diff silently drops remote edits during unsaved work
Data Flows & Wiring
mediumOpenModerate effortHigh blast radius
app/documents/page.tsx content_diff handler applies remote edits only when !hasUnsavedChanges — comment mentions 'or if the remote change is newer' but there is no timestamp comparison. Two concurrent editors get silent divergence with no conflict banner.
app/documents/page.tsx
Fix risk: Same collaboration conflict — merge strategy choice affects multi-editor docs sessions.
Fix effort: Add timestamp/version conflict UI or merge strategy when both editors have unsaved work.
344Driver Chat staff UI (/inbox) is English-only
Shanetrée, Routing & Driver Comms
mediumOpenModerate effort
app/inbox/InboxClient.tsx has zero useLanguage imports — thread list, AI toggle copy, send errors and settings strings are hardcoded English. Sibling /routing wraps LanguageProvider and bilingual t(en, es) throughout RoutingClient.tsx. Spanish-preference operators can use Shanetrée in Spanish but respond to drivers in an English-only inbox.
app/inbox/InboxClient.tsx
Fix effort: Wrap InboxClient copy with useLanguage/t(en,es) like RoutingClient.
345driver_chat_messages INSERT RLS does not constrain sender role
Security & Trust Boundaries
mediumOpenModerate effort
supabase/migrations/129_create_driver_chat.sql INSERT policy only checks company_id membership — any company member can insert rows with sender: 'driver'. /inbox correctly uses /api/driver-chat/send (forces sender: 'greco'), but a browser client could spoof driver messages directly.
supabase/migrations/129_create_driver_chat.sql
Fix effort: Migration: INSERT WITH CHECK sender IN ('greco','ai') for authenticated role, or force all inserts through send API only via revoked direct INSERT.
346driver_scan_reports has no CREATE TABLE migration in repo
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenModerate effort
lib/supabase/tool-reports.ts (~1928–1974) inserts/selects driver_scan_reports. Migrations only ALTER it (113_add_excluded_to_tool_report_tables.sql). Same migration-gap class as tracked greco_daily_stats and firmas_entries — fresh Supabase environments cannot reproduce Driver Scan persistence from migrations alone.
lib/supabase/tool-reports.ts
Fix effort: Add migration CREATE TABLE driver_scan_reports with company_id + RLS — export from hosted schema.
347Email HTML templates interpolate data without escaping
Email, v1 API & Entrée Panel Polish
mediumOpenModerate effort
lib/email/service.ts generateEmailHtml() embeds ${data.*} directly in HTML (anomaly_alert ~593–595, marketing ~461–462, new_chat_message preview ~416, agent_notification ~487–489). Crafted display names, previews or anomaly text in outbound data can inject HTML into company-branded email bodies.
lib/email/service.ts
Fix effort: HTML-escape all ${data.*} interpolations in generateEmailHtml — lib/email/service.ts.
348EmailService.sendWelcome() and agent_digest template are dead code
Kiosk Surfaces, Greco Tools & Platform Polish
mediumOpenModerate effort
lib/email/service.ts defines sendWelcome() (~323–332) with welcome HTML template (~357+) — grep finds zero callers outside the definition. agent_digest is registered in EmailTemplate (~26), mapped to weekly_digest prefs (~71), with HTML (~498+) but no route, cron or job worker invokes template: 'agent_digest'. Signup onboarding and agent weekly digest UI toggles have no sender path.
lib/email/service.ts
Fix effort: Wire sendWelcome on signup confirm + cron/job for agent_digest, or remove templates and UI toggles.
349firmas_entries has no CREATE TABLE migration
Greco Warehouse Operations
mediumOpenModerate effort
CindyVueClient saves driver signatures via saveFirmaEntry/getFirmasForDate/deleteFirmaEntry in lib/supabase/tool-reports.ts (~1867–1902) against firmas_entries. lib/agent/greco-tools.ts lists firmas_entries in allowed SQL tables. No migration file creates the table or RLS — same migration-gap class as greco_daily_stats but for DockSheet signature photos.
lib/supabase/tool-reports.tslib/agent/greco-tools.ts
Fix effort: Migration for firmas_entries + storage bucket refs; mirror greco_daily_stats RLS pattern.
350Four nested app/**/lib/ trees duplicate the top-level lib/ convention
Repository Structure Audit (Pass 23)
mediumOpenModerate effortMay affect neighbors
Domain logic also lives in app/cyclecount/lib/, app/drivers/lib/, app/transfers/lib/ and app/tools/greco/lib/ (items-engine, schedule-engine, excel-exports). Rest of the codebase uses lib/ at repo root. Inconsistent import mental model: some tools import @/lib/tools, others reach into app/tools/greco/lib. Consolidate Greco engines under lib/greco/ (today only greco-tools.ts + greco-cards.ts) or lib/tools/greco/.
app/cyclecount/lib/app/drivers/lib/app/transfers/lib/app/tools/greco/lib/
Fix risk: Deduping parallel implementations must update every copy or behavior drifts again.
Fix effort: Move app/tools/greco/lib/* to lib/greco/; same for cyclecount, drivers, transfers engines.
351Geronimo and Empty Slots deliberately dropped the Entrée demand pipeline
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenModerate effort
app/geronimo/page.tsx header (~18–22) says it was lifted out of Window Low Slots 'standalone of the entrée bridge — drop the Dakota xlsx and go, no ODBC dependency, no demand pipeline.' app/empty-slots/page.tsx (~16–17) has no Supabase reads/writes. Both consume BFC Dakota R08921s only — no fourWeekAvg short-dollar ranking, no arslot context, no tie to the ODBC demand that makes Window Low Slots actionable. Enterprise slot planning needs both planes on every surface.
app/geronimo/page.tsxapp/empty-slots/page.tsx
Fix effort: Optional ODBC demand overlay on Geronimo/Empty Slots (reuse /low-slots + dakota-merge) — mode flag for bridge-less vs full merge.
352GodView does N+1 queries and full-table scans
Performance & Scale
mediumOpenModerate effort
app/api/admin/godview/route.ts getCompanies() (~307–314) runs Promise.all of four count queries per company (profiles, quote_submissions, team_chat_messages, shared_documents) — 4×N companies per GodView Companies tab load. Agent section in app/components/GodView.tsx (~3430+) fetches all companies then per-company stats. app/api/monitoring/metrics/route.ts getJobMetrics (~142–165) selects all background_jobs.status rows into JS and filters in memory — no GROUP BY, no .limit().
app/api/admin/godview/route.tsapp/components/GodView.tsxapp/api/monitoring/metrics/route.ts
Fix effort: Replace per-company counts with SQL aggregate RPC; godview + monitoring routes.
353Greco dashboard stats fire 11 Supabase round-trips on every mount
Monolith Files & Bundle Weight
mediumOpenModerate effort
When isGreco is true, loadDashboardData (~1274–1293) runs Promise.all with seven head-count queries (forklift, picker, replen, returns, shorts, skips, truck_errors) plus four recent-activity selects — 11 round-trips before the Stats tab is ever opened. Non-Greco tenants skip this, but every Greco operator pays the cost on every dashboard load and refresh. A single RPC or materialized daily rollup would collapse this.
Fix effort: Single RPC or daily rollup view returning counts + recent activity — migration + dashboard loader swap; test non-Greco tenants still skip.
354Greco dashboard tab bar is overcrowded on mobile
UI, Flow & Density
mediumOpenModerate effort
Greco users get 10+ tab entries (Overview, Account, Shanetrée, DockSheet, Stats, Tools, AI, Workspace, Docs, Reports, Support) in one horizontal scroller at text-[10px] with only a right-edge gradient hint. Discoverability suffers — operators scroll sideways hunting for the tab they used yesterday; no grouping or 'More' overflow menu.
Fix effort: Group external links under More menu, collapse Greco shortcuts, or promote Workspace folder over duplicate tab entries.
355Greco kiosk/session APIs are world-readable by design
Security & Trust Boundaries
mediumOpenModerate effortCritical — test everything
GET /api/greco/session (full dock session for any date), /api/greco/analytics-log, /api/greco/custom-appointments, /api/port/ai-summary/memory and /api/entree/query serve operational data without authentication so kiosk pages (/inbound, /timer, /live/greco) work. Trade-off may be acceptable but is implicit — a shared kiosk token or documented allowlist would make the boundary deliberate.
Fix risk: Adding auth breaks /timer, /inbound, /live/greco, /noe without a kiosk token story — public GET is load-bearing for wall mounts.
Fix effort: Product decision first: shared kiosk JWT vs documented allowlist vs IP allowlist — then implement consistently across ~5 routes.
356History → chat deep link drops half its payload
Wiring & Broken Flows
mediumOpenModerate effort
“Comment on activity” links from the history page append ?prefill=…&activity=<id> to /team-chat, but team-chat only reads prefill and dm. The activity id — presumably meant to link the message back to the activity — is silently discarded. Similarly, /history?highlight=<id> is fully implemented on the read side (loads all, scrolls, highlights) but no code anywhere generates a highlight link.
Fix effort: Same activity param wiring.
357History → team-chat deep link drops the activity reference
Data Flows & Wiring
mediumOpenModerate effort
app/history/page.tsx 'Comment on activity' links append ?prefill=…&activity=<id> to /team-chat, but team-chat only reads prefill and dm params. The activity id is never consumed — messages can't be linked back to the ledger entry. /history?highlight=<id> is implemented on the read side but no code generates highlight links.
app/history/page.tsx
Fix effort: Read ?activity= in team-chat, attach to message metadata or thread context — needs schema or message prefix convention.
358History Excel export always fetches the unbounded ledger
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenModerate effortHigh blast radius
app/history/page.tsx handleExportExcel (~175–177) calls getUnifiedHistory(0) whenever on-screen loadLimit isn't already 'all'. Tracked issues cover UI 'All' mode; this export button is a separate path that always pulls the complete merged ledger (team_activity + dock + print + pull) for Excel generation — multi-megabyte export even when UI shows 50 rows.
app/history/page.tsx
Fix risk: Export explicitly calls getUnifiedHistory(0) — cap breaks 'entire history' workbook promise on the button label.
Fix effort: Server-side export endpoint with date range + cursor, or cap export at visible filter window.
359Hourly port-ai-summary cron can exceed Vercel maxDuration
API & Operations
mediumOpenModerate effort
Cron runs sequentially per company with maxDuration = 300 while each generatePortAISummaryForCompany() is multi-step Gemini. Two companies × ~3 minutes risks a platform kill mid-loop — partial companies updated, rest skipped until next hour with no resume cursor.
Fix effort: Per-company time budget, cursor/resume state, or split into one-company-per-invocation queue.
360IDE sandbox executes arbitrary user JavaScript
Security & Trust Boundaries
mediumOpenModerate effortMay affect neighbors
app/ide/page.tsx preview iframe uses sandbox='allow-scripts allow-modals' and injects editor contents via srcDoc including a <script> block (createSandboxDoc). Any code the user or AI assistant writes runs in browser with script execution — self-XSS / prototype-pollution vector on shared machines, even if isolated from the parent origin.
app/ide/page.tsx
Fix risk: Sandboxing breaks user snippets that depend on full DOM/window — IDE feature regression.
Fix effort: Tighten sandbox (drop allow-scripts for preview?), or run in isolated worker; balance IDE usefulness vs XSS on shared PCs.
361IDE semantic index stays empty — context search is non-functional
Dead Code & Orphan Features
mediumOpenModerate effort
ide_code_nodes is populated only by /api/ide/index, which has no UI caller. Dead routes /api/ide/context/search, /api/ide/patch and both assistant chat handlers read ide_code_nodes for symbol context — but with an empty table the 'semantic codebase awareness' feature can never return results. Appears functional in architecture docs; effectively useless on web IDE today.
Fix effort: Call /api/ide/index on workspace save from ide/page.tsx, or remove ide_code_nodes dependency from assistant routes.
362IDE workspaces are company-wide, not per-user
Build, IDE & Custom Tools
mediumOpenModerate effort
GET /api/ide/workspaces/route.ts (~21–26) lists all ide_workspaces for company_id with no created_by filter. Every company member sees and can open/edit/delete files in coworkers' workspaces via ide/workspaces/[id]/files. Tracked IDE silent errors and per-keystroke saves compound the risk.
Fix effort: Filter workspaces by created_by; add shared-workspace flag if collaboration intended.
363ide_user_settings table has no company_id column
Agent, Collaboration & Platform APIs
mediumOpenModerate effort
supabase/migrations/108_create_ide_user_settings.sql defines only user_id + settings blobs. Violates repo multi-tenancy rule (all tables need company_id REFERENCES companies). RLS is per-user only — no tenant anchor for audits, future multi-company users, or company-offboarding data cleanup.
supabase/migrations/108_create_ide_user_settings.sql
Fix effort: Migration adding company_id + backfill from profiles; update settings-sync route to set it.
364Inbound kiosk is English-only
Greco Warehouse Operations
mediumOpenModerate effort
app/inbound/InboundClient.tsx has zero useLanguage imports — all copy ('Scan PO / Item', schedule upload history, POSTAT labels, error strings) is hardcoded English. Warehouse floor kiosks (/inbound, /timer, /live/greco) are Spanish-first in other tools (Controls language toggle + greco-lang-pref), but the highest-traffic public lookup screen never switches locale.
app/inbound/InboundClient.tsx
Fix effort: Wrap InboundClient copy with useLanguage/t(en,es) like other Greco kiosk pages.
365Incentives has no server persistence or audit trail
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenModerate effort
app/incentives/IncentivesClient.tsx fetches routes from /api/public/shanetree-latest?modal=routes (~103) and computes case/stop payouts client-side. No upsertDailyStat, logReportGeneration, or Supabase writes under app/incentives/. Driver incentive numbers vanish on browser clear with no team_activity_log or Daily Stats entry.
app/incentives/IncentivesClient.tsxapp/incentives/
Fix effort: Add incentives_runs table + upsertDailyStat + logReportGeneration on weekly export — payroll audit requirement.
366Incentives payroll state lives only in global localStorage keys
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenModerate effortMay affect neighbors
app/incentives/IncentivesClient.tsx — EXCLUDED_KEY = 'greco.incentives.v1.excluded' and DAYS_KEY = 'greco.incentives.v1.days' (~23–24) with no per-user suffix (~34–53, 89–96). Shared warehouse PCs leak route exclusions and computed week history between operators — payroll-adjacent state should be server-backed or namespaced.
app/incentives/IncentivesClient.tsx
Fix risk: Namespacing keys resets shared-PC preferences until users reconfigure.
Fix effort: Namespace keys with userId or persist exclusions/days to Supabase incentives_settings table.
367Meat-count 'replace day' is delete-then-upsert without transaction
Backend & Database
mediumOpenModerate effortMay affect neighbors
lib/supabase/meat-counts.ts replaceMeatCountsForDate() (~289–297) awaits deleteMeatCountsForDate() then upsertMeatCountsForDate() as two separate Supabase round-trips — no RPC, no transaction. deleteMeatCountsForDate() (~304–311) DELETEs by company_id + count_date; overlapping uploads (double-click, two operators, retry) interleave so one run's delete can land mid-insert — partial or empty day snapshot.
lib/supabase/meat-counts.ts
Fix risk: RPC transaction changes failure mode — partial day visible during upload may become lock contention.
Fix effort: Single RPC doing DELETE+INSERT in one transaction in supabase migration.
368Meat-count “replace day” is delete-then-upsert with no transaction
Data Integrity & Races
mediumOpenModerate effortMay affect neighbors
replaceMeatCountsForDate() in lib/supabase/meat-counts.ts issues a DELETE for the (company, date) rows and then a separate bulk upsert. Two overlapping uploads (double-click, two operators, a retry) can interleave so one run's delete lands mid-way through the other's insert — leaving a partial or empty day snapshot. A single RPC doing both in one transaction closes the window.
lib/supabase/meat-counts.ts
Fix risk: Duplicate title — same concurrent upload interleave risk vs fix behavior.
Fix effort: Same transactional RPC.
369member_permissions.dashboard_tabs is stored but never applied
Permissions, Settings & Misleading UI
mediumOpenModerate effortHigh blast radius
getMyFullPermissions() returns dashboardTabs from member_permissions.dashboard_tabs (lib/supabase/member-permissions.ts ~513), but grep dashboardTabs in dashboard/page.tsx returns zero matches. Dashboard only consumes toolAccess from permissions (~1566). Per-member tab ACL is computed and persisted but no page reads it — useless schema field today.
lib/supabase/member-permissions.ts
Fix risk: Applying stored tabs suddenly hides Workspace cards admins thought they configured — latent settings activate.
Fix effort: Read dashboardTabs in dashboard/page.tsx and filter tabs/cards, or drop column from schema.
370Members, Support and History pages are English-only
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenModerate effort
app/members/page.tsx, app/support/page.tsx and app/history/page.tsx have zero useLanguage / t() usage. Tracked i18n gaps cover team-chat and documents — Spanish-preference Greco operators hit fully English member management, support tickets and activity ledger after switching language elsewhere.
app/members/page.tsxapp/support/page.tsxapp/history/page.tsx
Fix effort: Add useLanguage + t() passes on members, support, history pages — same as team-chat i18n effort.
371Monitoring metrics collectors fetch unbounded rows then count in Node
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenModerate effortMay affect neighbors
app/api/monitoring/metrics/route.ts — getJobMetrics() selects all background_jobs (~142–146); getApiMetrics() pulls every api_activity_log row in 24h (~184–193); getAnomalyMetrics() pulls all anomalies in 24h (~224–233). No SQL-side aggregation or .limit() — grows with tenant volume and slows founder monitoring polls.
app/api/monitoring/metrics/route.ts
Fix risk: Adding caps breaks export or load-all flows that depend on fetching entire tables.
Fix effort: Replace full selects with SQL count/aggregate RPCs or head:true count queries per metric bucket.
372Pass 22 dead-code hunt — ~2,400+ additional verified orphan lines
Dead Code Hunt (Pass 22)
mediumOpenModerate effort
Grep pass 22 beyond prior Dead Code categories: lib/agent/policy-verification.ts (~62 lines, zero imports), lib/routing/geocoder.ts (~109 lines, zero imports), lib/guide/printable-guide.ts (~1,200 lines, docs/whitepaper only), 13 unused Nivo render* exports in lib/agent/renderer.ts (~500+ lines), six ai-conversations CRUD/comment helpers (~250 lines), companyCache/statsCache write-only objects (~120 lines), npm packages ai + @ai-sdk/openai with zero source imports, plus ~25 orphan exports across member-permissions, chat-rooms, calendar, inbound alerts, Greco excel-exports, notifications, ai-filters, port-ai-summaries, shanetree-snapshots, greco-card-permissions, company-settings, individual-report-settings, documents, ai-chat-sessions, and duplicate type definitions. Total new maintainable surface that executes nothing in product flows: roughly 2,400–2,800 lines.
lib/agent/policy-verification.tslib/routing/geocoder.tslib/guide/printable-guide.tslib/agent/renderer.ts
Fix effort: Batch-delete verified orphans in policy-verification, geocoder, unused render* exports, ai-conversations CRUD, orphan supabase helpers, and npm ai/@ai-sdk/openai; run tsc after each batch.
373Permission presets API exists with no UI
Controls, Admin & Collaboration
mediumOpenModerate effort
getPermissionPresets, savePermissionPreset, deletePermissionPreset and applyPresetToMember in lib/supabase/member-permissions.ts (~524–666) have zero callers outside that file. permission_presets table + RLS in migration 057 are maintained with no Controls or Account surface — dead schema beside the live matrix UIs.
lib/supabase/member-permissions.ts
Fix effort: Add presets picker to Controls MemberPermissionsSection or drop permission_presets table.
374Profile avatars stored as inline base64 data URLs in profiles.avatar_url
Agent, Collaboration & Platform APIs
mediumOpenModerate effort
app/account/page.tsx (~749–753) reads avatar files with FileReader.readAsDataURL() and lib/supabase/account.ts (~148) persists the data URL to profiles.avatar_url. No file-size or MIME check; multi-megabyte images inflate profile rows and every presence/mention render that loads avatar_url. Should upload to Storage and store a URL (distinct from quote-attachment JSONB issue).
app/account/page.tsxlib/supabase/account.ts
Fix effort: Upload to Supabase Storage avatars bucket; store public URL; add 2MB cap — account page + migration for existing rows optional.
375Public kiosk relay routes have no rate limiting
Security & Trust Boundaries
mediumOpenModerate effortCritical — test everything
app/api/public/inbound-relay, driver-chat and po-doc have zero withRateLimit calls (grep finds none under app/api/public/*). Each can enqueue bridge/ODBC relay jobs or trigger inline AI replies — abusable for cost and bridge load beyond the already-tracked item-alert-subscribe gap.
app/api/public/inbound-relayapp/api/public/
Fix risk: Rate limits on /api/greco/session, /api/entree/query, will-call relay can block wall mounts during receiving rush.
Fix effort: Add withRateLimit to inbound-relay, driver-chat and po-doc — per-IP for anon kiosk callers.
376QStash-down marks ai_analysis jobs completed without running AI
API & Operations
mediumOpenModerate effort
lib/jobs/service.ts executeAIAnalysisJob() is a stub returning { success: true } with no gateway call. When QStash is unavailable, enqueueJob falls back to synchronous execution and marks those background_jobs completed while doing zero analysis.
lib/jobs/service.ts
Fix effort: Implement real executeAIAnalysisJob or mark jobs failed when QStash unavailable instead of stub success.
377Rate limiting fails open, and the Redis path races
Rate Limits & Abuse Surface
mediumOpenModerate effortHigh blast radius
lib/rate-limit.ts returns allowed: true whenever the check_rate_limit RPC errors (unless RATE_LIMIT_FAIL_CLOSED=true, which isn't set), and requests carrying no bearer token and no companyId skip limiting entirely — so public routes that call withRateLimit without auth context are effectively unlimited. The Redis API-key fast path in lib/api/auth.ts is also check-then-increment (two separate ops), so concurrent bursts slip past the per-minute cap.
lib/rate-limit.tslib/api/auth.ts
Fix risk: Fail-closed change denies access on transient errors — opposite of today's permissive behavior.
Fix effort: Same rate-limit hardening.
378Rate limiting fails open; several routes have none
API & Operations
mediumOpenModerate effortHigh blast radius
lib/rate-limit.ts returns allowed when no companyId/bearer token and on RPC errors unless RATE_LIMIT_FAIL_CLOSED=true. /api/public/item-alert-subscribe, /api/dockvoo/notify and /api/jobs/enqueue have no withRateLimit at all.
lib/rate-limit.ts
Fix risk: RATE_LIMIT_FAIL_CLOSED=true or adding limits to dockvoo/notify can drop legitimate burst traffic from forklift save loops.
Fix effort: Set RATE_LIMIT_FAIL_CLOSED in prod, add withRateLimit to public routes, fix Redis race with atomic INCR — several files.
379Report getters with no row caps
Scalability & Performance
mediumOpenModerate effortMay affect neighbors
getAllPickerReports, getShortsHistory (when uncapped), getAllDriverScanReports, getUserFiles and getCompanyDocuments all select without .limit() — and several embed full JSONB report payloads per row. The picker page then flattens every row of every report into memory for client-side filters. Each works now; each degrades linearly with usage and none has a ceiling.
Fix risk: Adding caps breaks export or load-all flows that depend on fetching entire tables.
Fix effort: Same row caps + pagination.
380Report/document getters have no row caps
Performance & Scale
mediumOpenModerate effortMay affect neighbors
lib/supabase/tool-reports.ts getAllPickerReports() (~494–512) .select('*') on picker_reports with no .limit() — embeds full picker_data + short_runner_data JSONB per row. lib/supabase/documents.ts getCompanyDocuments() (~82) selects all shared_documents. app/picker/page.tsx (~234–241) flattens every row of every report into allReportsData for client-side crew/name/scan filters. getShortsHistory (uncapped) and getUserFiles follow same pattern — linear degradation, no ceiling.
lib/supabase/tool-reports.tslib/supabase/documents.tsapp/picker/page.tsx
Fix risk: Adding caps breaks export or load-all flows that depend on fetching entire tables.
Fix effort: Add sensible .limit() defaults + pagination params to 4–5 supabase getters; update consuming pages.
381Returns Intel trends by BFC Client ID with no Entrée arcusto bridge
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenModerate effort
app/docs/page.tsx (~1858–1859) documents Returns Intel expecting BFC Client ID per location. app/returns-intel/page.tsx aggregates week-over-week by that string with no lookup to Entrée CUSTNO/COMPANY (arcusto). Return Diffs normCust strips leading zeros (~20748) but Returns Intel does not — location-level return trends cannot roll up to salesman, credit terms, or Entrée customer master for enterprise reporting.
app/docs/page.tsxapp/returns-intel/page.tsx
Fix effort: Lookup table or ODBC /customer?cust= join enriching Returns Intel rows with Entrée CUSTNO/COMPANY/salesman.
382Routing client calls OSRM directly from the browser
Shanetrée, Routing & Driver Comms
mediumOpenModerate effort
fetchOSRMRoute() in RoutingClient.tsx (~1232) hits https://router.project-osrm.org/route/v1/driving/… client-side with errors swallowed. No server proxy, auth or rate limit — distinct from tracked /api/routing/geocode (Nominatim). Road-distance mode exposes dispatcher client IPs to a third-party demo service and breaks if OSRM throttles or blocks the warehouse egress IP.
Fix effort: Proxy OSRM via /api/routing/osrm with rate limit; or bundle self-hosted routing.
383Several Greco kiosk endpoints are world-readable by design — worth an explicit decision
Security & Trust Boundaries
mediumOpenModerate effortCritical — test everything
GET /api/greco/session (full dock session data for any date), /api/greco/analytics-log, /api/greco/custom-appointments, /api/port/ai-summary/memory and the /api/entree/query bridge proxy serve data without authentication so the public kiosk pages (/inbound, /timer, /live/greco) work. That trade-off may be acceptable, but it's currently implicit — a shared kiosk-token or at least a documented allowlist would make the boundary deliberate rather than accidental.
Fix risk: Locking down public kiosk APIs without token replacement breaks wall-mounted workflows.
Fix effort: Same kiosk boundary decision + implementation.
384Shorts page (~4,327 lines) eagerly imports recharts on a data-entry route
Monolith Files & Bundle Weight
mediumOpenModerate effortHigh blast radius
app/shorts/page.tsx is the fourth-largest page file. It static-imports six recharts components (~25) and lib/supabase/tool-reports save/get helpers while also hosting upload parsing, row editing, history drawer, and AI summary UI in one component. Dock supervisors opening Shorts for a quick entry form download charting libraries before any chart renders. Split charts behind dynamic() or a ShortsCharts child.
app/shorts/page.tsxlib/supabase/tool-reports
Fix risk: Large single-file page — extracting or refactoring risks breaking embedded business logic.
Fix effort: Extract ShortsCharts child with dynamic() recharts import — 1–2 files; verify chart tab still renders after upload.
385Signup page performs no work-email domain validation
Public API, Jobs, Onboarding & Greco Gaps
mediumOpenModerate effort
app/signup/page.tsx calls supabase.auth.signUp({ email, password }) (~23) with no domain allowlist/denylist before ensureUserProfile() (~26). Pairs with ensure_user_profile auto-company creation — personal emails create orphan tenants with full founder access.
app/signup/page.tsx
Fix effort: Client + server domain allowlist or block personal-email providers before ensureUserProfile.
386Six IDE API routes have no web UI callers
Dead Code & Orphan Features
mediumOpenModerate effort
app/ide/page.tsx fetches only /api/ide/workspaces/* and /api/ide/assistant/chat (non-stream). These routes have zero fetch callers: /api/ide/index (~114 lines), /api/ide/patch (~462), /api/ide/context/search (~89), /api/ide/settings-sync (~89), /api/ide/assistant/complete (~99), /api/ide/assistant/chat/stream (~251). ~1,100 lines of semantic-index / patch / completion infrastructure maintained for a desktop extension that never calls them from the web IDE.
app/ide/page.tsx
Fix effort: Wire web IDE to index/patch/context routes or delete ~1,100 lines if desktop-only extension is abandoned.
387Supabase browser client loaded from CDN with major-only pin
Code Quality & Hygiene
mediumOpenModerate effort
app/layout.tsx loads https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2 (major-only) while package.json pins ^2.91.1 for server usage. lib/supabase/client.ts createClient() reads window.supabase from the CDN script, not the npm bundle — supply-chain exposure and version skew between CDN and server paths.
app/layout.tsxsupabase/supabase-jslib/supabase/client.ts
Fix effort: Bundle @supabase/supabase-js via npm import in client.ts, remove CDN script from layout — test auth persistence on warehouse machines.
388Support chat has no realtime and swallows failures
Data Flows & Wiring
mediumOpenModerate effortMay affect neighbors
app/support/page.tsx loads messages once on mount — no postgres_changes subscription or polling, so admin replies don't appear until refresh or the user sends again. getMySupportMessages() returns [] on error; handleSend never surfaces result.error. Failed loads look like 'No messages yet'; failed sends look like a no-op.
app/support/page.tsx
Fix risk: Realtime filter changes alter what live updates appear — easy to hide needed events.
Fix effort: Add postgres_changes subscription (like team-chat) + surface send/load errors in UI.
389Tab visibility only hides two Overview shortcut cards
Permissions, Settings & Misleading UI
mediumOpenModerate effort
company_tab_settings / adminHiddenTabs is loaded on dashboard but consulted only twice — to hide Overview cards for tools and reports (~1838, ~1872). The tab strip, Workspace folder cards and <Link href='/team-chat'> never read adminHiddenTabs. Hiding 'Chat' or 'Members' in Account/Controls does nothing visible outside two card removals.
Fix effort: Apply adminHiddenTabs to tab strip, Workspace folder and external links — or rename feature 'Overview card visibility'.
390Team-chat per-room notification bell is cosmetic
Dead Code & Orphan Features
mediumOpenModerate effortHigh blast radius
team-chat exposes a notifications on/off toggle per room (chat_notification_settings table, lib/supabase/chat-rooms.ts toggleNotification). UI updates notifications_enabled and persists to DB, but handleSendMessage never reads the flag — no email, push, badge or mute logic consults it on send. Users think they muted a room; nothing changes. Useless control (~80 lines UI + table writes).
lib/supabase/chat-rooms.ts
Fix risk: Wiring latent toggles suddenly enables or blocks behavior users assumed was active.
Fix effort: Implement mute check on send (email/badge) or remove toggle and chat_notification_settings table.
391The /scan page re-implements the tool detector
Dead Code & Orphans
mediumOpenModerate effort
app/scan/page.tsx has its own copy of the column-matching/scoring logic that lib/tools/detector.ts already provides — the two can drift apart. Additionally, scan redirects to Reconciliation, Loader and Returns Intel with a sessionStorage scannerFile those three pages never read, so users arrive with nothing loaded (nine other tools do consume it).
app/scan/page.tsxlib/tools/detector.ts
Fix effort: Same detector consolidation.
392Thirteen migration numbers are used twice
Backend & Database
mediumOpenModerate effort
supabase/migrations/ has colliding prefixes at 027, 028, 029, 030, 031, 032, 033, 055, 094, 095, 100, 130 and 132. Apply order depends on alphabetical tie-breaking; fresh environments may sequence conflicting changes differently than production.
supabase/migrations/
Fix effort: Rename conflicting files to unique sequential numbers — must not re-run on prod; document apply order for fresh envs only.
393Transfers/overstock is BFC-only — no Entrée on-hand or slot validation
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenModerate effort
app/docs/page.tsx (~788–806): Transfers ingests BFC Total Overstock xlsx (Hecht/Brewster/Rana tabs) only. No join to Entrée ONHAND, itemwhselocs, or arslot to validate whether BFC overstock positions match ERP inventory. Subs modal already correlates replenishment risk with DockSheet QTYREC — standalone Transfers and Replen (two BFC uploads, app/replen/page.tsx ~237, ~379) lack that receiving-bridge context.
app/docs/page.tsxapp/replen/page.tsx
Fix effort: Enrich Transfers rows with Entrée ONHAND + arslot via /stockvoo or item lookup — moderate ODBC fan-out per upload.
394v1 file download loads entire blob into memory with no streaming cap
Email, v1 API & Entrée Panel Polish
mediumOpenModerate effort
app/api/v1/files/[id]/route.ts (~56–64) uses Buffer.from(fileContent) with no size guard. Large user_files payloads can OOM serverless handlers — parallel to tracked JSONB bulk rows but specific to v1 files download path.
app/api/v1/files/
Fix effort: Stream from Storage with max size check, or reject files >N MB in files/[id]/route.ts.
395v1 schedules POST sets next_run_at to now without cron math
Email, v1 API & Entrée Panel Polish
mediumOpenModerate effort
app/api/v1/schedules/route.ts (~158–174) comments 'Calculate next run time' but assigns nextRunAt = now.toISOString() regardless of cron_expression / interval_seconds / timezone. Schedules never executing is tracked — even with a runner, first-run timing is wrong on create.
app/api/v1/schedules/route.ts
Fix effort: Use cron-parser or interval math to compute next_run_at from cron_expression + timezone.
396Verified dead-code inventory (~3,600 lines + 5 missing workers)
Dead Code & Orphan Features
mediumOpenModerate effortMay affect neighbors
Grep-verified orphans across this pass: ~720 lines of unused lib/supabase exports (legacy team-chat CRUD, team-activity helpers, anomalies client dupes, quote/chat/port/rate-limit helpers), ~74 lines unused lib/tools/index.ts barrel, ~800 lines duplicate /produce page, ~1,900 lines of API routes with zero fetch callers (six IDE routes, monitoring pair, desktop-download, cache HTTP), plus subscribeToTeamMessages (~50 lines, tracked separately in Hygiene). lib/jobs/service.ts maps five worker URLs (report, embedding, scheduled-report, export, cleanup) that have no route files — enqueue paths 404. Total maintainable surface that executes nothing useful: roughly 3,400–3,800 lines today, plus broken job types.
lib/supabaselib/tools/index.tslib/jobs/service.ts
Fix risk: Bulk delete may remove bridge-invoked routes (internal autopull, desktop-download) — grep callers per file, not batch.
Fix effort: Roll-up cleanup pass — delete verified orphans in batches (lib exports, unwired routes, /produce) and implement or remove the five missing job workers.
397weekly_digest and marketing_emails toggles have no sender
Permissions, Settings & Misleading UI
mediumOpenModerate effort
Account shows weekly_digest and marketing_emails (~91–100). Grep under app/api/cron finds no digest or marketing cron. No UI path checks these prefs before sending — they only exist in lib/email/service.ts template mapping and the open /api/email/send relay allowlist. Users opt in/out of emails that are never sent.
app/api/cronlib/email/service.ts
Fix effort: Implement digest/marketing crons that respect prefs, or remove toggles from account UI.
398Will-call kiosk APIs beyond relay trio have no rate limiting
Window, Sales & Kiosk Scheduling
mediumOpenModerate effort
Tracked public relay gap names inbound-relay, driver-chat and po-doc only. Sibling unauthenticated routes also lack withRateLimit: /api/public/window-appointments (POST spam + slot polling), /api/public/sales-relay (ODBC job enqueue), /api/public/will-call-print-request (print-queue flood), /api/public/invoice-status (live ODBC + relay), /api/public/shanetree-latest (full snapshot reads).
Fix effort: Add withRateLimit to window-appointments, sales-relay, will-call-print-request, invoice-status, shanetree-latest.
399willcall-print-claim is a non-atomic SELECT-then-UPDATE race
Warehouse Tool Coverage, Monitoring & Collaboration
mediumOpenModerate effortMay affect neighbors
app/api/internal/willcall-print-claim/route.ts (~56–81) reads oldest pending will_call_print_requests row, then updates by id + status=pending. Two bridge processes can race; loser returns { row: null } with no retry signal. Unlike shanetree_relay_claim RPC, no FOR UPDATE SKIP LOCKED — duplicate print claims or missed jobs under concurrent bridge workers.
app/api/internal/willcall-print-claim/route.ts
Fix risk: Atomic claim RPC changes double-print vs missed-print tradeoff on /noe kiosk.
Fix effort: Add claim_willcall_print_request RPC with FOR UPDATE SKIP LOCKED — mirror shanetree_relay_claim pattern.
400/port, /noe and /adrian mount the full EntreeClient chunk
Monolith Files & Bundle Weight
mediumOpenHard / riskyCritical — test everything
app/port/page.tsx, app/noe/page.tsx and app/adrian/page.tsx each static-import EntreeClient with a mode prop (portMode, noeMode, adrianMode). Relay tablets on /port and sales kiosks on /noe download the same ~42k-line Shanetrée bundle as full /entree even though most modal tiles are hidden per mode. Route-level dynamic import or a thin mode shell would let each entry point ship a smaller first chunk.
app/port/page.tsxapp/noe/page.tsxapp/adrian/page.tsx
Fix risk: EntreeClient monolith — kiosk and ERP panels share state; drive-by edits risk production dock workflows.
Fix effort: Route-level code splitting or thin mode shells that dynamic-import only the tiles each kiosk needs — must not break relay/print flows.
401CindyVueClient statically chains ~5k lines of schedule + items engines
Monolith Files & Bundle Weight
mediumOpenHard / riskyCritical — test everything
app/tools/greco/CindyVueClient.tsx (6,003 lines) statically imports parseXlsxAndBuildSchedule and export helpers from schedule-engine.ts (2,448 lines) and items logic from items-engine.ts (2,543 lines). Opening /cindyvue pulls ~11k lines of scheduling/parsing code into the first JS chunk. schedule-engine uses exceljs types at module scope; CindyVue also imports lib/supabase/tool-reports for firma photos and daily stats.
app/tools/greco/CindyVueClient.tsxlib/supabase/tool-reports
Fix risk: DockSheet receiving schedule is business-critical; lazy-loading or splitting can break 30s autosave and grid sync paths.
Fix effort: Dynamic-import schedule-engine on file upload; consider server-side parse API — CindyVue is operationally critical, needs dock-floor QA.
402Dakota merge math is triplicated — drift breaks WMS↔ERP number parity
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenHard / riskyCritical — test everything
lib/lowslots/dakota-merge.ts (~11–17) documents a faithful port of inline EntreeClient parseDakotaSlotXlsx + mergeWithDakota that 'MUST stay in lockstep.' lib/lowslots/pick-list-xlsx.ts carries the same warning. Three copies (EntreeClient + two lib files) cannot import each other without refactoring the 42k-line monolith. Any Dakota column tweak must be applied thrice or /port relay and in-modal Low Slots diverge — unacceptable for enterprise sign-off.
lib/lowslots/dakota-merge.tslib/lowslots/pick-list-xlsx.ts
Fix risk: Consolidating merge logic in lib/ must preserve EntreeClient modal behavior; wrong merge breaks slot counts operators trust for replen.
Fix effort: Extract parseDakotaSlotXlsx + mergeWithDakota into lib/lowslots/core.ts; EntreeClient imports it — pairs with Entree monolith split, high regression risk.
403Dashboard dark mode ends at the dashboard
Aesthetics & Consistency
mediumOpenHard / risky
app/dashboard/page.tsx (~201–207, ~464–487) persists dashboard-theme in localStorage and toggles .dashboard-dark-mode on the dashboard root only (background #0f172a at ~1687). app/layout.tsx sets --page-bg on body but Account, Docs, Members and Greco tools use default light shells. Navigating from a dark dashboard to /account snaps to white — comment at ~466 explicitly says 'only this dashboard flips'. Site-wide theme would need layout-level class or next-themes.
app/dashboard/page.tsxapp/layout.tsx
Fix effort: Same site-wide theme vs WebKit trade-off.
404dashboard/page.tsx is a ~3,500-line shell with almost no code splitting
Monolith Files & Bundle Weight
mediumOpenHard / riskyHigh blast radius
app/dashboard/page.tsx is 3,496 lines — auth, Greco tool grid, email settings modals, team activity, documents, AI conversations, custom cards/tools, and admin tabs all live in one component. Only GodView is dynamic() (~63); everything else (including 20+ lib/supabase imports) loads on first dashboard visit. Extracting tab panels (Files, Team, Settings, Greco cards) behind dynamic() would shrink the default route chunk.
app/dashboard/page.tsxlib/supabase
Fix risk: Tab lazy-load changes mount timing for 30s refresh, Greco grid, and settings modals — dashboard is the hub for every other route.
Fix effort: Extract tab panels behind dynamic() — touches auth bootstrap, Greco grid, and settings modals; multi-day refactor with dashboard regression testing.
405Escape key handling in Shanetrée closes over stale modal state
Wiring & Broken Flows
mediumOpenHard / riskyHigh blast radius
EntreeClient's global Escape handler closes ~a dozen modals, but the effect's dependency array only lists three of those state values — the listener captures stale booleans, so Escape can no-op on a freshly-opened modal until an unrelated dep changes. The Drivers tool has a cousin bug: its requestAnimationFrame timer loop has no unmount cleanup.
Fix risk: Fixing deps in 41k-line EntreeClient — high regression risk; dedicated tested pass required.
Fix effort: Fix useEffect deps in 41k-line EntreeClient.tsx — high regression risk; needs dedicated tested pass.
406Forklift productivity is BFC-only — no tie to Entrée labor, receiving or PO context
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenHard / risky
app/forklift/page.tsx + lib/tools/processors/forklift.ts parse Day/Night BFC Task Summary xlsx into forklift_reports. No bridge endpoint, no join to Entrée SECID/employee master, no correlation with DockSheet QTYREC or /po-doc receiving lines. For enterprise ops, forklift TPH should eventually connect to what was received and what was still short — today it is an isolated leaderboard metric.
app/forklift/page.tsxlib/tools/processors/forklift.ts
Fix effort: Join forklift_reports to DockSheet session_date + /po-doc QTYREC rollup — needs date/employee keys and optional ODBC pull on save.
407GodView disable-user toggle is cosmetic
Controls, Admin & Collaboration
mediumOpenHard / riskyHigh blast radius
toggleUserStatus in app/api/admin/godview/route.ts (~941–950) only updates profiles.is_disabled — inline comment admits 'would require auth admin API to actually disable users.' GodView 'disable user' does not block Supabase Auth login; flag is informational only unless something else reads it.
app/api/admin/godview/route.ts
Fix risk: Wiring latent toggles suddenly enables or blocks behavior users assumed was active.
Fix effort: Call Supabase Auth admin ban API or hide toggle until profiles.is_disabled is enforced on login.
408lib/supabase/ is a flat 43-file directory with no domain subpackages
Repository Structure Audit (Pass 23)
mediumOpenHard / risky
All Supabase client modules live in lib/supabase/*.ts at one depth — team-chat.ts, chat-rooms.ts, shanetree-snapshots.ts, member-permissions.ts, etc. No lib/supabase/chat/, reports/, greco/, or settings/ folders. Workable at current size but already hard to navigate; professional repos group by bounded context and co-locate types/tests per domain.
lib/supabase/lib/supabase/chat/
Fix effort: Split into lib/supabase/chat, reports, greco, settings with barrel re-exports — wide import path churn.
409lib/supabase/tool-reports.ts (~2,170 lines) is in every warehouse tool bundle
Monolith Files & Bundle Weight
mediumOpenHard / riskyHigh blast radius
lib/supabase/tool-reports.ts holds save/get helpers for forklift, picker, shorts, skips, replen, returns, truck-errors, loader, reconciliation, daily stats, firma uploads, and notification hooks — 2,170 lines. Grep shows 15+ client pages static-importing it (picker, shorts, forklift, CindyVue, EntreeClient, drivers, daily-stats, etc.). Tree-shaking helps but type exports and shared upsertDailyStat still pull a large shared module into most Greco routes.
lib/supabase/tool-reports.ts
Fix risk: Large single-file page — extracting or refactoring risks breaking embedded business logic.
Fix effort: Split into per-tool modules (picker-reports.ts, shorts-reports.ts, etc.) or server actions — wide import graph across 15+ pages.
410No middleware — page auth is client-side only
Auth & Session
mediumOpenHard / riskyCritical — test everything
There is no middleware.ts. Every protected route checks supabase.auth.getUser() in a client useEffect and redirects after mount. RLS protects data, but unauthenticated visitors briefly render page shells and each page re-implements its own guard differently.
Fix risk: Central auth changes affect every protected route and public kiosk exception list.
Fix effort: Add middleware.ts with Supabase session cookie check + redirect — touches every protected route's guard logic; high regression surface.
411Realtime broadcast channels have no membership authorization
Agent, Collaboration & Platform APIs
mediumOpenHard / riskyMay affect neighbors
lib/realtime/broadcast.ts (~49–54) builds broadcast:${scope}:${resourceId} from predictable inputs. Any logged-in Supabase client knowing a document UUID can subscribe and send cursor, typing, content_diff, selection and scene_action events. No server-side channel ACL — collaboration spoofing on shared docs is possible if IDs leak.
lib/realtime/broadcast.ts
Fix risk: Realtime filter changes alter what live updates appear — easy to hide needed events.
Fix effort: Supabase Realtime channel auth or server-minted channel tokens — requires RLS on realtime.messages or custom auth hook.
412Returns+ (BFC) and Window Returns modal (ODBC) are never reconciled
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenHard / risky
app/returns/page.tsx categorizes BFC export columns (Item, Rtn Cd, Customer Name, Orig. Inv#). Window Returns modal pulls live artrans/arytran QTYSHP<0 via ODBC (shanetree-whitepaper.ts ~58, ~84). No tool compares Returns+ categorized output against Entrée ODBC returns the way Return Diffs does for credit memos — another BFC-vs-ERP gap where only one side got the three-way diff treatment.
app/returns/page.tsx
Fix effort: New diff modal or extend Return Diffs to compare Returns+ categories vs ODBC QTYSHP<0 — similar snapshot pattern to return_diffs_snapshots.
413Root layout and globals.css hard-lock light color-scheme
UI, Colors & Aesthetics
mediumOpenHard / risky
app/layout.tsx sets html style colorScheme:'light' plus meta tags; app/globals.css lines 6–8 force color-scheme: light !important. This is intentional for WebKit gradient fixes but means dashboard's dark-mode toggle (localStorage dashboard-theme) can't propagate — leaving /dashboard snaps every other page back to forced light.
app/layout.tsxapp/globals.css
Fix effort: Site-wide theme requires reconciling WebKit gradient fix (globals.css !important) with dashboard dark mode — architectural CSS conflict.
414Routing autosave is last-writer-wins on full stops JSONB
Shanetrée, Routing & Driver Comms
mediumOpenHard / risky
Debounced autosave in RoutingClient.tsx (~1130–1140) POSTs the entire stops array after every edit. POST /api/routing/routes upserts greco_routes by id with no version column or optimistic lock (routes/route.ts ~80–102). Two dispatcher tabs editing the same route overwrite each other's stop order — parallel to tracked DockSheet session autosave but greco_routes is not named there.
Fix effort: Add updated_at optimistic lock or merge strategy on greco_routes POST.
415ScheduleOutlookClient duplicates schedule-engine logic verbatim
Monolith Files & Bundle Weight
mediumOpenHard / riskyHigh blast radius
app/schedule-outlook/ScheduleOutlookClient.tsx (2,278 lines) comments at ~72 and ~298 say logic was 'copied verbatim' from app/tools/greco/lib/schedule-engine.ts. Bug fixes and performance tweaks must be applied twice; bundle weight doubles for anyone who uses both DockSheet and the Outlook binder. Extracting a shared @/lib/greco/schedule-core module is the fix — moderate refactor with regression risk on colored exports.
app/schedule-outlook/ScheduleOutlookClient.tsxapp/tools/greco/lib/schedule-engine.tslib/greco/schedule-core
Fix risk: Deduping into shared module must update DockSheet colored exports and Outlook binder in lockstep — bugfix currently applied twice for a reason.
Fix effort: Extract shared schedule-core module used by both CindyVue and Outlook — must verify colored exports and combine-by-load parity on real xlsx files.
416Seven copies of 'resolve the user's company'
Backend & Database
mediumOpenHard / riskyHigh blast radius
tool-reports, shanetree-snapshots, shanetree-pull-log, shanetree-print-log, shanetree-manual-subs, account-geocodes and shanetree-relay each re-implement profile lookup + fallback + profile repair, and they've drifted (relay skips profile repair; company-settings returns null with no fallback).
Fix risk: Fixing one resolver without the other six reintroduces drift; relay skips profile repair, company-settings returns null — behaviors differ intentionally today.
Fix effort: Extract shared resolveCompanyId() in lib/supabase/ and replace 7 implementations — wide diff, each tool path needs smoke test.
417Seven copies of “resolve the user's company”
Database & Migrations
mediumOpenHard / riskyHigh blast radius
tool-reports, shanetree-snapshots, shanetree-pull-log, shanetree-print-log, shanetree-manual-subs, account-geocodes and shanetree-relay each re-implement profile lookup + fallback + profile repair, and they've already drifted (relay skips profile repair; company-settings returns null with no fallback). Beyond the known Greco auto-assign problem, the duplication itself means any fix has to land seven times. One shared resolveCompanyId() is overdue.
Fix risk: Duplicate audit title — same seven-way resolver drift risk.
Fix effort: Same shared helper extraction.
418Subs modal proves DockSheet↔PO receiving join — that pattern isn't platform-wide
Enterprise WMS ↔ ERP Integration (BFC · Entrée · Greco)
mediumOpenHard / riskyHigh blast radius
shanetree-whitepaper.ts §5.19: Subs export pulls prior-day DockSheet session POs and live /po-doc QTYREC per PURNO to flag whether receipts cover next-day OOS demand (EntreeClient.tsx ~12637, ~13189, ~13779). This is meaningful WMS(scheduler)+ERP(receiving) intimacy. Standalone Replen, Negative Slots, Loader and Thruput never consume greco_cindyvue_sessions or /po-doc — the join logic lives only inside one Shanetrée modal instead of a reusable lib/greco/receiving-bridge module.
lib/greco/receiving-bridge
Fix risk: Extracting receiving-bridge from EntreeClient modal risks breaking Subs export that flags OOS vs receipts.
Fix effort: Extract lib/greco/receiving-bridge.ts from Subs modal; wire Replen/Transfers/Loader to greco_cindyvue_sessions + /po-doc QTYREC.
419Two spreadsheet stacks, two chart stacks, two screenshot stacks
Dependencies & Build Hygiene
mediumOpenHard / riskyHigh blast radius
exceljs and xlsx are both installed for the same job (see previous item); recharts powers ~14 pages while ten @nivo/* packages exist solely for AgentGenerativeUI; html2canvas and html-to-image each serve one DOM-to-image call site. Every pair is duplicated bundle weight and divergent behavior. Also, @types/leaflet sits in dependencies instead of devDependencies.
Fix risk: Spreadsheet parser changes affect every upload tool — cell types and dates may shift.
Fix effort: Dependency consolidation: drop xlsx OR exceljs, drop html2canvas OR html-to-image, audit nivo usage — wide bundle impact.
420Monolith page files make any cross-cutting change risky
Code Quality & Hygiene
mediumOpenMajor refactorHigh blast radius
wc -l: app/entree/EntreeClient.tsx ~41,917; app/tools/greco/CindyVueClient.tsx ~6,076; app/shorts/page.tsx ~4,327; app/dashboard/page.tsx ~3,496. Loader unification and back-navigation had to tip-toe around them. Any cross-cutting UI change (shared modal, a11y, error banner) risks merge conflicts — gradual panel extraction lowers blast radius.
app/entree/EntreeClient.tsxapp/tools/greco/CindyVueClient.tsxapp/shorts/page.tsxapp/dashboard/page.tsx
Fix risk: Same monolith blast radius — cross-cutting UI in 41k-line EntreeClient is not a safe drive-by fix.
Fix effort: Gradual panel extraction from EntreeClient/CindyVue — weeks of careful work, not one PR.
421Monolith page files make UI evolution risky
Maintainability (UI-relevant)
mediumOpenMajor refactorHigh blast radius
wc -l: app/entree/EntreeClient.tsx ~41,917; app/tools/greco/CindyVueClient.tsx ~6,076; app/shorts/page.tsx ~4,327; app/dashboard/page.tsx ~3,496. Loader unification and back-navigation pass tip-toed around these — any cross-cutting UI change (shared modal, a11y, error banner) risks merge conflicts across tens of thousands of lines. Gradual panel extraction lowers blast radius.
app/entree/EntreeClient.tsxapp/tools/greco/CindyVueClient.tsxapp/shorts/page.tsxapp/dashboard/page.tsx
Fix risk: Any extraction from EntreeClient/CindyVueClient/shorts/dashboard risks breaking dock-floor panels that share local state — loader pass deliberately avoided these.
Fix effort: Same long-term decomposition.
422Multi-megabyte JSONB rows in Postgres
Performance & Scale
mediumOpenMajor refactor
greco_cindyvue_sessions stores list_data + grid_data + history_log JSONB per dock day; shanetree_snapshots holds full ODBC modal payloads; quote_submissions.file_attachments embeds base64 in JSONB (app/quote/page.tsx); picker_reports.raw_input_data and report JSONB columns grow unbounded. lib/supabase/user-files.ts correctly uses Storage — these older tables bypass object storage and will dominate backup size and query latency.
app/quote/page.tsxlib/supabase/user-files.ts
Fix effort: Migrate dock sessions, snapshots, quote attachments to Supabase Storage — large data migration, URL rewiring, backward compat.
423One env var UUID gates the entire warehouse suite
Multi-Tenancy Hardcodes
mediumOpenMajor refactorCritical — test everything
NEXT_PUBLIC_GRECO_COMPANY_ID is load-bearing in ~15 places: GrecoOnlyGuard on every warehouse tool, the session/driver-chat/will-call/item-alert public APIs, unified history's dock-event merge, and dashboard card logic. Plus greco_-prefixed tables (greco_daily_stats, greco_routes, greco_card_permissions) and name-matching on 'greco'. Fine as long as FileDisplay serves one warehouse operator — but it's the single biggest refactor standing between this codebase and a second customer, and worth a deliberate decision rather than gradual accretion.
Fix risk: Removing GRECO_COMPANY_ID hardcode requires threading real tenant context through 29 tools, kiosks, and public APIs — partial migration breaks Greco-only routes for everyone.
Fix effort: Multi-warehouse onboarding refactor — company-scoped tables, remove greco_ prefix pattern, ~15+ env references.
424The dock session table isn't multi-tenant
Backend & Database
mediumOpenMajor refactorCritical — test everything
greco_cindyvue_sessions has no company_id column. POST app/api/greco/session/route.ts (~176–186) upserts on onConflict: 'session_date' only — list_data/grid_data/history_log keyed globally per calendar day. Writer check (~155–160) verifies profile.company_id === GRECO_COMPANY_ID env UUID, not per-row tenant. DockSheet, /live/greco, /timer and /inbound share one global session row per date — second warehouse impossible without schema change.
app/api/greco/session/route.ts
Fix risk: Adding company_id + RLS touches DockSheet, /live/greco, /timer, /inbound, CindyVueClient autosave, and every onConflict: session_date upsert — second warehouse is a schema migration plus full stack retest.
Fix effort: Add company_id + RLS to greco_cindyvue_sessions and thread through entire DockSheet/live/timer stack — prerequisite for second warehouse customer.