refactor(app): fix type-aware lint errors in voice, tabs store, host-connection, audio recorder (#706)

Clear all type-aware lint errors in four app state/runtime files:

- voice-context.tsx: bind runtime methods before passing to useSyncExternalStore
  and object spreads to satisfy the unbound-method rule
- workspace-tabs-store.ts: replace unsafe `as` casts with isPlainRecord type
  predicate + toObjectRecord; add coerceWorkspaceTabTarget to bridge unknown
  storage data to WorkspaceTabTarget without assertions
- host-connection.ts: same isPlainRecord/toObjectRecord pattern; replace
  String(record.x ?? "") calls with typeof guards to fix no-base-to-string
- use-audio-recorder.native.ts: construct real Blob instead of casting an
  object literal; restructure useEffect for consistent-return; drop await on
  void recorder.record(); use instanceof Error for message extraction; remove
  redundant Boolean() wrapping
This commit is contained in:
Mohamed Boudra
2026-05-05 19:58:16 +07:00
parent c8274ba88a
commit 660b4d3cec
4 changed files with 107 additions and 95 deletions
+59 -35
View File
@@ -41,6 +41,14 @@ export function buildWorkspaceTabPersistenceKey(input: {
return `${serverId}:${workspaceId}`;
}
function isPlainRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function toObjectRecord(value: unknown): Record<string, unknown> | undefined {
return isPlainRecord(value) ? value : undefined;
}
function normalizeTabOrder(list: unknown): string[] {
if (!Array.isArray(list)) {
return [];
@@ -94,17 +102,6 @@ function buildNextTabsForEnsure(args: {
);
}
interface LegacyPersistedWorkspaceTabsState {
version?: number;
state?: unknown;
openTabsByWorkspace?: Record<string, WorkspaceTab[]>;
uiTabsByWorkspace?: Record<string, WorkspaceTab[]>;
focusedTabIdByWorkspace?: Record<string, string>;
tabOrderByWorkspace?: Record<string, string[]>;
lastFocusedTabByWorkspace?: Record<string, unknown>;
tabOrderLegacyByWorkspace?: Record<string, string[]>;
}
interface MigrationRawSources {
rawUiTabsByWorkspace: Record<string, unknown>;
rawFocused: Record<string, unknown>;
@@ -113,42 +110,69 @@ interface MigrationRawSources {
}
function extractMigrationRawSources(persistedState: unknown): MigrationRawSources {
const legacy = persistedState as LegacyPersistedWorkspaceTabsState | undefined;
const rawState = ((legacy as { state?: Record<string, unknown> } | undefined)?.state ??
legacy ??
{}) as Record<string, unknown>;
const top = toObjectRecord(persistedState) ?? {};
const rawState = toObjectRecord(top.state) ?? top;
return {
rawUiTabsByWorkspace: (rawState.uiTabsByWorkspace ??
rawState.openTabsByWorkspace ??
legacy?.uiTabsByWorkspace ??
legacy?.openTabsByWorkspace ??
{}) as Record<string, unknown>,
rawFocused: (rawState.focusedTabIdByWorkspace ??
legacy?.focusedTabIdByWorkspace ??
rawState.lastFocusedTabByWorkspace ??
{}) as Record<string, unknown>,
rawOrder: (rawState.tabOrderByWorkspace ??
legacy?.tabOrderByWorkspace ??
rawState.tabOrderByWorkspace ??
{}) as Record<string, unknown>,
legacyOrder: (rawState.tabOrderByWorkspace ??
rawState.tabOrderLegacyByWorkspace ??
{}) as Record<string, unknown>,
rawUiTabsByWorkspace:
toObjectRecord(
rawState.uiTabsByWorkspace ??
rawState.openTabsByWorkspace ??
top.uiTabsByWorkspace ??
top.openTabsByWorkspace,
) ?? {},
rawFocused:
toObjectRecord(
rawState.focusedTabIdByWorkspace ??
rawState.lastFocusedTabByWorkspace ??
top.focusedTabIdByWorkspace,
) ?? {},
rawOrder: toObjectRecord(rawState.tabOrderByWorkspace ?? top.tabOrderByWorkspace) ?? {},
legacyOrder:
toObjectRecord(
rawState.tabOrderByWorkspace ??
rawState.tabOrderLegacyByWorkspace ??
top.tabOrderLegacyByWorkspace,
) ?? {},
};
}
function coerceWorkspaceTabTarget(raw: Record<string, unknown>): WorkspaceTabTarget | null {
const kind = typeof raw.kind === "string" ? raw.kind : null;
if (kind === "draft" && typeof raw.draftId === "string") {
return normalizeWorkspaceTabTarget({ kind: "draft", draftId: raw.draftId });
}
if (kind === "agent" && typeof raw.agentId === "string") {
return normalizeWorkspaceTabTarget({ kind: "agent", agentId: raw.agentId });
}
if (kind === "terminal" && typeof raw.terminalId === "string") {
return normalizeWorkspaceTabTarget({ kind: "terminal", terminalId: raw.terminalId });
}
if (kind === "browser" && typeof raw.browserId === "string") {
return normalizeWorkspaceTabTarget({ kind: "browser", browserId: raw.browserId });
}
if (kind === "file" && typeof raw.path === "string") {
return normalizeWorkspaceTabTarget({ kind: "file", path: raw.path });
}
if (kind === "setup" && typeof raw.workspaceId === "string") {
return normalizeWorkspaceTabTarget({ kind: "setup", workspaceId: raw.workspaceId });
}
return null;
}
function migrateSingleTab(rawTab: unknown): WorkspaceTab | null {
if (!rawTab || typeof rawTab !== "object") {
const record = toObjectRecord(rawTab);
if (!record) {
return null;
}
const normalizedTarget = normalizeWorkspaceTabTarget((rawTab as WorkspaceTab).target);
const rawTarget = toObjectRecord(record.target);
const normalizedTarget = rawTarget ? coerceWorkspaceTabTarget(rawTarget) : null;
if (!normalizedTarget) {
return null;
}
const rawTabId = trimNonEmpty((rawTab as WorkspaceTab).tabId);
const rawTabId = trimNonEmpty(typeof record.tabId === "string" ? record.tabId : null);
const tabId = rawTabId ?? buildDeterministicWorkspaceTabId(normalizedTarget);
const rawCreatedAt = (rawTab as WorkspaceTab).createdAt;
const rawCreatedAt = record.createdAt;
return {
tabId,
target: normalizedTarget,