fix(web): clear the 84 type errors blocking check-types

`apps/web` extends a tsconfig with `noUncheckedIndexedAccess`, so index
access and a few genuine defects had accumulated behind a gate that has
never been green.

Most of it traced to one expression: `useProject().selectedProject` was
`normalizedProjects[0]`, typed `string | undefined` even though the array is
never empty. Defaulting it to the same tag the array falls back to fixed ~20
errors across chat, add-document, app-experience, quick-note-card and the
note modals in one line.

The rest:

- orbit-memory: type `ring` as `0 | 1` and RR/SPEED as tuples so ring lookups
  stay `number`, iterate INTEG with `.entries()`, guard the comet refs, and
  fall back to an empty icon when a node has no key
- memory-graph `use-graph-data` and `graph-card`: iterate with `.entries()`
  and skip edges whose endpoints are missing instead of indexing blind
- chat history buckets and user initials: optional access on fixed-shape
  arrays that TypeScript cannot narrow
- `stores/chat`: drop the `msgA.content !== msgB.content` comparison —
  UIMessage keeps its text in `parts`, so both sides were always undefined —
  and type the conversations fallback so entries aren't `unknown`
- `useResetOrganization`: `retry: 0`, since the object form of RetryOptions
  needs type/baseDelay/maxDelay
- SyncLogoIcon: accept the `style` prop timeline-view already passes it

One real bug surfaced on the way: the iOS-shortcut mutation in settings
returned `res.key` from `authClient.apiKey.create()`, which resolves to
`{ data, error }`. The key was always undefined, so the modal showed nothing
and the clipboard copy had nothing to copy. It now unwraps the result the same
way the Raycast mutation right below it does.
This commit is contained in:
rajashidattapy 2026-08-20 00:42:51 +05:30
parent 18a2dfbe39
commit f0baf904ff
10 changed files with 67 additions and 50 deletions

View file

@ -1612,11 +1612,11 @@ export function ChatSidebar({
]
for (const t of filtered) {
const ts = new Date(t.updatedAt).getTime()
if (ts >= startOfToday) buckets[0].items.push(t)
else if (ts >= startOfToday - day) buckets[1].items.push(t)
else if (ts >= startOfToday - 7 * day) buckets[2].items.push(t)
else if (ts >= startOfToday - 30 * day) buckets[3].items.push(t)
else buckets[4].items.push(t)
if (ts >= startOfToday) buckets[0]?.items.push(t)
else if (ts >= startOfToday - day) buckets[1]?.items.push(t)
else if (ts >= startOfToday - 7 * day) buckets[2]?.items.push(t)
else if (ts >= startOfToday - 30 * day) buckets[3]?.items.push(t)
else buckets[4]?.items.push(t)
}
return buckets.filter((b) => b.items.length > 0)
}, [threads, historySearch])

View file

@ -67,15 +67,13 @@ export function StaticGraphPreview({
const result: { x1: number; y1: number; x2: number; y2: number }[] = []
const edgeCount = Math.min(nodes.length - 1, 20)
for (let i = 0; i < edgeCount; i++) {
const a = Math.floor(rand() * nodes.length)
let b = Math.floor(rand() * nodes.length)
if (b === a) b = (a + 1) % nodes.length
result.push({
x1: nodes[a]?.x,
y1: nodes[a]?.y,
x2: nodes[b]?.x,
y2: nodes[b]?.y,
})
const aIdx = Math.floor(rand() * nodes.length)
let bIdx = Math.floor(rand() * nodes.length)
if (bIdx === aIdx) bIdx = (aIdx + 1) % nodes.length
const a = nodes[aIdx]
const b = nodes[bIdx]
if (!a || !b) continue
result.push({ x1: a.x, y1: a.y, x2: b.x, y2: b.y })
}
return result
}, [nodes])

View file

@ -48,7 +48,7 @@ const INTEG: {
key?: string
label?: string
sub?: string
ring: number
ring: 0 | 1
ang: number
size: number
dim: boolean
@ -76,8 +76,8 @@ const DW = 780
const DH = 1024
const CX = 390
const CY = 512
const RR = [200, 335]
const SPEED = [1.0, 0.78]
const RR: [number, number] = [200, 335]
const SPEED: [number, number] = [1.0, 0.78]
const DRAW: [number, number, number, string][] = [
[125, 0.28, 1.7, "#74a8f6"],
[200, 0.18, 1.9, "#4389ff"],
@ -167,16 +167,15 @@ export default function OrbitMemory({
track.setAttribute("stroke-opacity", "0")
dtrack.setAttribute("stroke-opacity", "0")
for (let k = 0; k < NC; k++) {
cms[k].setAttribute("opacity", "0")
cmgs[k].setAttribute("opacity", "0")
oms[k].setAttribute("opacity", "0")
omgs[k].setAttribute("opacity", "0")
cms[k]?.setAttribute("opacity", "0")
cmgs[k]?.setAttribute("opacity", "0")
oms[k]?.setAttribute("opacity", "0")
omgs[k]?.setAttribute("opacity", "0")
}
}
let angle = 0
const place = () => {
for (let i = 0; i < INTEG.length; i++) {
const it = INTEG[i]
for (const [i, it] of INTEG.entries()) {
const a = ((it.ang + angle * SPEED[it.ring]) * Math.PI) / 180
const R = RR[it.ring]
const ux = CX + R * Math.cos(a)
@ -199,8 +198,8 @@ export default function OrbitMemory({
const DISC = 700
const INN: number[] = []
const OUT: number[] = []
for (let i = 0; i < INTEG.length; i++) {
;(INTEG[i].ring === 0 ? INN : OUT).push(i)
for (const [i, it] of INTEG.entries()) {
;(it.ring === 0 ? INN : OUT).push(i)
}
let phase = "orbit"
let pT0 = 0
@ -259,12 +258,16 @@ export default function OrbitMemory({
const ay = pyc - uy * HL
const bx = pxc + ux * HL
const by = pyc + uy * HL
setLine(gr[k], ax, ay, bx, by)
setLine(cr[k], ax, ay, bx, by)
setLine(gl[k], ax, ay, bx, by)
const grad = gr[k]
const comet = cr[k]
const glow = gl[k]
if (!grad || !comet || !glow) continue
setLine(grad, ax, ay, bx, by)
setLine(comet, ax, ay, bx, by)
setLine(glow, ax, ay, bx, by)
const o = Math.sin(f * Math.PI) * P
cr[k].setAttribute("opacity", o.toFixed(2))
gl[k].setAttribute("opacity", (0.6 * o).toFixed(2))
comet.setAttribute("opacity", o.toFixed(2))
glow.setAttribute("opacity", (0.6 * o).toFixed(2))
}
}
const frame = (now: number) => {
@ -276,13 +279,13 @@ export default function OrbitMemory({
if (phase === "orbit" && pt > ORBIT) {
phase = "receive"
pT0 = now
src = INN[(ci * 5) % INN.length]
dst = OUT[(ci * 7) % OUT.length]
src = INN[(ci * 5) % INN.length] ?? 0
dst = OUT[(ci * 7) % OUT.length] ?? 0
ci++
nodes[src]?.classList.add("sm-active")
stage.style.setProperty(
"--cm",
`rgb(${ACCENT[INTEG[src].key!] || "91,157,255"})`,
`rgb(${ACCENT[INTEG[src]?.key ?? ""] || "91,157,255"})`,
)
} else if (phase === "receive" && pt > RECV) {
phase = "send"
@ -318,8 +321,8 @@ export default function OrbitMemory({
seg(
now,
track,
INTEG[src]._x!,
INTEG[src]._y!,
INTEG[src]?._x ?? 0,
INTEG[src]?._y ?? 0,
ps,
grads,
cms,
@ -330,8 +333,8 @@ export default function OrbitMemory({
seg(
now,
dtrack,
INTEG[dst]._x!,
INTEG[dst]._y!,
INTEG[dst]?._x ?? 0,
INTEG[dst]?._y ?? 0,
pd,
ogrs,
oms,
@ -508,7 +511,9 @@ export default function OrbitMemory({
<span
className="sm-glyph"
// biome-ignore lint/security/noDangerouslySetInnerHtml: static, vetted inline brand SVG markup
dangerouslySetInnerHTML={{ __html: ICONS[it.key!] }}
dangerouslySetInnerHTML={{
__html: (it.key && ICONS[it.key]) || "",
}}
/>
<span className="sm-gsub">{it.sub}</span>
</span>
@ -518,7 +523,9 @@ export default function OrbitMemory({
<span
className="sm-glyph"
// biome-ignore lint/security/noDangerouslySetInnerHtml: static, vetted inline brand SVG markup
dangerouslySetInnerHTML={{ __html: ICONS[it.key!] }}
dangerouslySetInnerHTML={{
__html: (it.key && ICONS[it.key]) || "",
}}
/>
)}
</div>

View file

@ -152,7 +152,10 @@ export default function Integrations() {
name: `ios-${generateId().slice(0, 8)}`,
prefix: `sm_${org?.id}_`,
})
return res.key
if (res.error)
throw new Error(res.error.message ?? "Failed to create API key")
if (!res.data?.key) throw new Error("API key missing from response")
return res.data.key
},
onSuccess: (key) => {
setApiKey(key)

View file

@ -79,9 +79,11 @@ export function UserProfileMenu({
const initials = (() => {
if (user.name) {
const parts = user.name.trim().split(/\s+/)
const first = parts[0] ?? ""
const last = parts[parts.length - 1] ?? ""
return parts.length >= 2
? `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase()
: parts[0].slice(0, 2).toUpperCase()
? `${first.slice(0, 1)}${last.slice(0, 1)}`.toUpperCase()
: first.slice(0, 2).toUpperCase()
}
if (user.email) return user.email.slice(0, 2).toUpperCase()
return "SM"

View file

@ -11,7 +11,7 @@ export function useResetOrganization() {
mutationFn: async (body: { confirmation: string }) => {
const res = await $fetch("@post/settings/reset", {
body,
retry: { attempts: 0 },
retry: 0,
})
if (res.error) {
const e = res.error as Record<string, unknown>

View file

@ -27,8 +27,6 @@ export function areUIMessageArraysEqual(
return false
}
if (msgA.content !== msgB.content) return false
if (JSON.stringify(msgA.parts) !== JSON.stringify(msgB.parts)) {
return false
}
@ -202,7 +200,8 @@ export function usePersistentChat() {
)
const conversations: ConversationSummary[] = (() => {
const convs = projectState?.conversations ?? {}
const convs: Record<string, ConversationRecord> =
projectState?.conversations ?? {}
return Object.entries(convs).map(([id, rec]) => ({
id,
title: rec.title,

View file

@ -21,7 +21,7 @@ export function useProject() {
const normalizedProjects =
selectedProjects.length === 0 ? [defaultTag] : selectedProjects
const selectedProject = normalizedProjects[0]
const selectedProject = normalizedProjects[0] ?? defaultTag
const effectiveContainerTags = normalizedProjects

View file

@ -507,8 +507,7 @@ export function useGraphData(
// Golden angle (~137.5 deg) produces optimal packing in a spiral
const goldenAngle = Math.PI * (3 - Math.sqrt(5))
for (let docIdx = 0; docIdx < docCount; docIdx++) {
const doc = documents[docIdx]
for (const [docIdx, doc] of documents.entries()) {
const docCluster = getDocumentClusterAssignment(doc, clusterAssignments)
const angle = docIdx * goldenAngle
const radius = spiralScale * Math.sqrt((docIdx + 1) / docCount)

View file

@ -1,3 +1,5 @@
import type { CSSProperties } from "react"
export const OneDrive = ({ className }: { className?: string }) => (
<svg
className={className}
@ -237,7 +239,13 @@ export const PDF = ({ className }: { className?: string }) => (
</svg>
)
export const SyncLogoIcon = ({ className }: { className?: string }) => {
export const SyncLogoIcon = ({
className,
style,
}: {
className?: string
style?: CSSProperties
}) => {
return (
<svg
width="11"
@ -246,6 +254,7 @@ export const SyncLogoIcon = ({ className }: { className?: string }) => {
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
style={style}
>
<title>Sync Logo</title>
<g clipPath="url(#clip0_344_4856)">