types: clear remaining tsc errors across 41 files (76→14, behavior-preserving)

Per-file type-only fixes surfaced after the Supabase client was correctly
typed. All fixes preserve runtime behavior; no any/ts-ignore/ts-expect-error.

- lib/supabase/client.ts: annotate the browser client as SupabaseClient<Database>
  (same ssr/js drift as server.ts; fixes it centrally for all callers).
- Supabase embedded-select joins whose FK metadata is missing from the
  generated types are bridged with `as unknown as <target>` + a TODO to remove
  after regenerating types/database.ts.
- setState optimistic-update variance and raw <select> enum values cast to the
  precise element/union type; .insert payload fields (Json, enums) given the
  precise column type via satisfies/field casts.
- Added narrowing guards where a value was legitimately nullable (comms channel,
  public form page) without changing the intended path.

The 14 remaining errors are confined to the four files fixed in Phase B (real
bugs + the tautological actions.test rewrite). 639 tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Moheeb Zara
2026-08-01 18:56:11 -07:00
co-authored by Claude Opus 4.8
parent 1766780bca
commit b1edb5c460
41 changed files with 148 additions and 59 deletions
+1 -1
View File
@@ -96,7 +96,7 @@ export default function KbEntryEditor({ entry, member }: Props) {
<label className="font-sans text-xs font-medium text-muted-foreground block mb-1.5">Visibility</label>
<select
value={visibility}
onChange={e => setVisibility(e.target.value)}
onChange={e => setVisibility(e.target.value as 'board' | 'all_members' | 'admin_only')}
className="w-full bg-background border border-border rounded px-3 py-2 font-sans text-sm text-foreground focus:outline-none focus:border-primary transition"
>
<option value="all_members">All Members</option>
+2 -1
View File
@@ -3,6 +3,7 @@ import { notFound } from 'next/navigation'
import Link from 'next/link'
import { ArrowLeft } from 'lucide-react'
import KbEntryEditor from './kb-entry-editor'
import type { Tables } from '@/types/database'
export default async function OpsEntryPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
@@ -19,5 +20,5 @@ export default async function OpsEntryPage({ params }: { params: Promise<{ id: s
if (!entry) notFound()
return <KbEntryEditor entry={entry} member={member} />
return <KbEntryEditor entry={entry} member={member as Tables<'space_members'>} />
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { createClient } from '@/lib/supabase/server'
import type { Tables } from '@/types/database'
import KbEntryEditor from '../[id]/kb-entry-editor'
export default async function NewOpsEntryPage() {
@@ -10,5 +11,5 @@ export default async function NewOpsEntryPage() {
.from('space_members').select('space_id, role, display_name').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single()
if (!member) return null
return <KbEntryEditor member={member} />
return <KbEntryEditor member={member as Tables<'space_members'>} />
}
+11 -5
View File
@@ -9,7 +9,7 @@ import { revealSecret, deleteSecret } from '@/lib/actions/secrets'
import { OpsAclEditor } from '@/components/ops/ops-acl-editor'
import { createClient } from '@/lib/supabase/client'
import { toast } from 'sonner'
import type { Tables, TablesInsert } from '@/types/database'
import type { Tables, TablesInsert, TablesUpdate, Enums } from '@/types/database'
import { PageTitle } from '@/components/ui/page-title'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { useConfirm } from '@/components/ui/confirm'
@@ -135,7 +135,7 @@ function KbModal({
<label className="font-sans text-xs font-medium text-muted-foreground block mb-1.5">Visibility</label>
<select
value={visibility}
onChange={e => setVisibility(e.target.value)}
onChange={e => setVisibility(e.target.value as Enums<'kb_visibility'>)}
className="w-full bg-background border border-border rounded px-3 py-2 font-sans text-sm text-foreground focus:outline-none focus:border-primary transition"
>
<option value="all_members">All Members</option>
@@ -212,7 +212,11 @@ function AddSecretModal({
label: title.trim(),
area: area.trim() || null,
value: value.trim(),
}).select('id, title, area, created_at').single()
// `satisfies` keeps the payload type-checked; the trailing `as never`
// only bridges the typed browser client whose .insert() generic collapses
// to `never`.
// TODO(types): remove after regenerating types/database.ts (missing FK relationship metadata)
} satisfies TablesInsert<'secrets'> as never).select('id, title, area, created_at').single()
setSaving(false)
if (error) { toast.error(error.message); return }
toast.success('Secret saved')
@@ -298,14 +302,16 @@ function AreaLeadModal({
area_name: areaName.trim(),
lead_handle: memberName.trim(),
description: contactInfo.trim() || null,
}).eq('id', lead.id).select('*').single()
// TODO(types): remove `as never` after regenerating types/database.ts (missing FK relationship metadata)
} satisfies TablesUpdate<'area_leads'> as never).eq('id', lead.id).select('*').single()
} else {
result = await supabase.from('area_leads').insert({
space_id: spaceId,
area_name: areaName.trim(),
lead_handle: memberName.trim(),
description: contactInfo.trim() || null,
}).select('*').single()
// TODO(types): remove `as never` after regenerating types/database.ts (missing FK relationship metadata)
} satisfies TablesInsert<'area_leads'> as never).select('*').single()
}
setSaving(false)
if (result.error) { toast.error(result.error.message); return }
+1 -1
View File
@@ -55,7 +55,7 @@ export default async function OpsPage() {
spaceId={member.space_id}
kbEntries={kbEntries ?? []}
areaLeads={areaLeads ?? []}
secrets={secretsResult.data ?? []}
secrets={(secretsResult.data ?? []) as Tables<'secrets'>[]}
canSeeSecrets={canSeeSecrets}
canManageAcl={canManageAcl}
aclRoleOptions={aclRoleOptions}