authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-12 23:46:19-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-12 23:46:19-07:00
log20a8f1de3668e0ff4da236d75258e2184a68d727
tree890d47d0b4be2104662ee7d15e33af64ac0d02a5
parentdb6e0531e4faac0fc5ac0e12088b01bed6e7b851
signature Signed by PGP key B5690EEEBB952194

chore(mobile): enable noUncheckedIndexedAccess and noImplicitOverride (#11538)

Co-authored-by: Claude Code <noreply@anthropic.com>

11 files changed, 67 insertions(+), 34 deletions(-)

apps/mobile/modules/t3-markdown-text/src/NativeMarkdownSelectableText.tsx+1-1
......@@ -98,7 +98,7 @@ function resolveHeadingFontSize(textStyle: NativeMarkdownTextStyle, headingLevel
9898 }
9999
100100 const scale = textStyle.fontSize / DEFAULT_BODY_FONT_SIZE;
101 return Math.max(12, Math.round(DEFAULT_HEADING_FONT_SIZES[index] * scale));
101 return Math.max(12, Math.round((DEFAULT_HEADING_FONT_SIZES[index] ?? 15) * scale));
102102}
103103
104104function runStyle(run: NativeMarkdownTextRun, textStyle: NativeMarkdownTextStyle): TextStyle {
apps/mobile/scripts/generate-uniwind-themes.mts+3-3
......@@ -53,7 +53,7 @@ const color = (family: TailwindColorFamily, shade?: TailwindColorShade, opacity
5353// These replace the remaining dark:* utility pairs. A registered palette theme is
5454// neither literally `light` nor `dark`, so appearance-sensitive values must also be
5555// represented as semantic variables for custom themes.
56const ADAPTIVE_COLORS = {
56const ADAPTIVE_COLORS: Readonly<Record<string, readonly [light: string, dark: string]>> = {
5757 "--color-adaptive-amber-50-950-a40": [color("amber", 50), color("amber", 950, 0.4)],
5858 "--color-adaptive-amber-200-900-a60": [color("amber", 200), color("amber", 900, 0.6)],
5959 "--color-adaptive-amber-500-a12-a16": [color("amber", 500, 0.12), color("amber", 500, 0.16)],
......@@ -143,9 +143,9 @@ export const customThemeNames = BUILT_IN_THEME_IDS.flatMap((themeId) =>
143143
144144const adaptiveVariablesFor = (appearance: MobileThemeAppearance) =>
145145 Object.fromEntries(
146 Object.entries(ADAPTIVE_COLORS).map(([name, values]) => [
146 Object.entries(ADAPTIVE_COLORS).map(([name, [light, dark]]) => [
147147 name,
148 values[appearance === "light" ? 0 : 1],
148 appearance === "light" ? light : dark,
149149 ]),
150150 );
151151
apps/mobile/src/components/AndroidAnchoredMenu.tsx+1-1
......@@ -125,7 +125,7 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) {
125125 return () => subscription.remove();
126126 }, [anchor, close, submenuDepth]);
127127
128 const parent = path.length > 0 ? path[path.length - 1] : null;
128 const parent = path[path.length - 1] ?? null;
129129 const levelActions = (parent?.subactions ?? props.actions).filter(
130130 (action) => !(action.attributes?.hidden ?? false),
131131 );
apps/mobile/src/features/review/nativeReviewDiffAdapter.ts+4-1
......@@ -53,7 +53,7 @@ function opaqueNativeHexColor(color: string, background: string): string {
5353 const alpha = rgba[4] === undefined ? 1 : Math.min(1, Math.max(0, Number(rgba[4])));
5454 const channels = [1, 2, 3].map((index) => {
5555 const foreground = Number(rgba[index]);
56 const behind = Number.parseInt(backgroundHex[index], 16);
56 const behind = Number.parseInt(backgroundHex[index] ?? "0", 16);
5757 return Math.round(foreground * alpha + behind * (1 - alpha));
5858 });
5959 return `#${channels.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
......@@ -358,6 +358,9 @@ function addNativeWordDiffRanges(
358358 for (let pairIndex = 0; pairIndex < pairedCount; pairIndex += 1) {
359359 const deletedRowIndex = deletedRowIndexes[pairIndex];
360360 const addedRowIndex = addedRowIndexes[pairIndex];
361 if (deletedRowIndex === undefined || addedRowIndex === undefined) {
362 continue;
363 }
361364 const deletedRow = nextRows[deletedRowIndex];
362365 const addedRow = nextRows[addedRowIndex];
363366 if (!deletedRow?.content || !addedRow?.content) {
apps/mobile/src/features/review/reviewModel.test.ts+2-2
......@@ -84,7 +84,7 @@ describe("buildReviewSectionItems", () => {
8484 },
8585 ];
8686
87 const loadedTurnId = getReviewSectionIdForCheckpoint(checkpoints[0]);
87 const loadedTurnId = getReviewSectionIdForCheckpoint(checkpoints[0]!);
8888 const items = buildReviewSectionItems({
8989 checkpoints,
9090 gitSections,
......@@ -92,7 +92,7 @@ describe("buildReviewSectionItems", () => {
9292 [loadedTurnId]: "diff --git a/loaded.ts b/loaded.ts",
9393 },
9494 loadingTurnIds: {
95 [getReviewSectionIdForCheckpoint(checkpoints[1])]: true,
95 [getReviewSectionIdForCheckpoint(checkpoints[1]!)]: true,
9696 },
9797 loadingGitSections: false,
9898 });
apps/mobile/src/features/terminal/terminalTheme.ts+27-3
......@@ -1,4 +1,8 @@
1import { BUILT_IN_THEMES, getThemeColorsForAppearance } from "@t3tools/shared/themePalettes";
1import {
2 BUILT_IN_THEMES,
3 T3_CHAT_THEME,
4 getThemeColorsForAppearance,
5} from "@t3tools/shared/themePalettes";
26
37import {
48 getMobileThemeVariables,
......@@ -15,9 +19,29 @@ export interface TerminalTheme {
1519 readonly border: string;
1620 readonly cursorForeground: string;
1721 readonly cursorBackground: string;
18 readonly palette: readonly string[];
22 /** The 16 ANSI colors, in order. A fixed tuple so indexed reads are never undefined. */
23 readonly palette: TerminalPalette;
1924}
2025
26type TerminalPalette = readonly [
27 string,
28 string,
29 string,
30 string,
31 string,
32 string,
33 string,
34 string,
35 string,
36 string,
37 string,
38 string,
39 string,
40 string,
41 string,
42 string,
43];
44
2145const PIERRE_LIGHT_THEME: TerminalTheme = {
2246 // Pierre terminal palette with the app's shared screen background.
2347 background: "#f2f2f7",
......@@ -85,7 +109,7 @@ export function getMobileTerminalTheme(
85109 const base = getPierreTerminalTheme(scheme);
86110 if (themeId === "t3-code" || themeId === "material-you") return base;
87111
88 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? BUILT_IN_THEMES[0];
112 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;
89113 const palette = getThemeColorsForAppearance(theme, scheme) ?? theme.colors;
90114 const colors = getMobileThemeVariables(themeId, scheme);
91115 const background = themeColorToNativeColor(palette.terminalBackground);
apps/mobile/src/lib/mobileTheme.test-support.ts+1-1
......@@ -14,7 +14,7 @@ export function readDefaultMobileThemeVariables(
1414 return Object.fromEntries(
1515 Array.from(variant.matchAll(/(--color-[a-z0-9-]+):\s*([^;]+);/gu), ([, name, value]) => [
1616 name,
17 value.trim(),
17 (value ?? "").trim(),
1818 ]),
1919 ) as MobileThemeVariables;
2020}
apps/mobile/src/lib/mobileTheme.test.ts+6-6
......@@ -1,5 +1,5 @@
11import { describe, expect, it } from "vite-plus/test";
2import { BUILT_IN_THEME_IDS, BUILT_IN_THEMES } from "@t3tools/shared/themePalettes";
2import { BUILT_IN_THEME_IDS, BUILT_IN_THEMES, T3_CHAT_THEME } from "@t3tools/shared/themePalettes";
33import { readDefaultMobileThemeVariables } from "./mobileTheme.test-support";
44
55import {
......@@ -49,7 +49,7 @@ function compositeOver(overlay: string, background: string): string {
4949
5050describe("mobile themes", () => {
5151 it("declares every runtime theme variable in the static stylesheet", () => {
52 const generatedVariables = createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light");
52 const generatedVariables = createMobileThemeVariables(T3_CHAT_THEME.colors, "light");
5353 expect(Object.keys(readDefaultMobileThemeVariables("light")).sort()).toEqual(
5454 Object.keys(generatedVariables).sort(),
5555 );
......@@ -152,16 +152,16 @@ describe("mobile themes", () => {
152152 });
153153
154154 it("maps semantic palette roles onto every mobile color variable", () => {
155 const variables = createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light");
155 const variables = createMobileThemeVariables(T3_CHAT_THEME.colors, "light");
156156 expect(Object.keys(variables)).toHaveLength(75);
157157 expect(variables["--color-sheet-solid"]).toBe(
158 themeColorToNativeColor(BUILT_IN_THEMES[0].colors.chrome),
158 themeColorToNativeColor(T3_CHAT_THEME.colors.chrome),
159159 );
160160 expect(variables["--color-warning"]).toBe(
161 themeColorToNativeColor(BUILT_IN_THEMES[0].colors.warningSurface),
161 themeColorToNativeColor(T3_CHAT_THEME.colors.warningSurface),
162162 );
163163 expect(variables["--color-warning-foreground"]).toBe(
164 themeColorToNativeColor(BUILT_IN_THEMES[0].colors.warningForeground),
164 themeColorToNativeColor(T3_CHAT_THEME.colors.warningForeground),
165165 );
166166 expect(variables["--color-primary"]).not.toBe(variables["--color-screen"]);
167167 expect(variables["--color-primary-shadow"]).toBe("#000000");
apps/mobile/src/lib/mobileTheme.ts+14-14
......@@ -1,5 +1,6 @@
11import {
22 BUILT_IN_THEMES,
3 T3_CHAT_THEME,
34 getThemeColorsForAppearance,
45 MOBILE_DEFAULT_THEME_ID,
56 MOBILE_THEME_IDS as SHARED_MOBILE_THEME_IDS,
......@@ -29,7 +30,9 @@ export const MOBILE_THEME_OPTIONS: ReadonlyArray<{
2930 ...BUILT_IN_THEMES.map((theme) => ({ id: theme.id as MobileThemeId, label: theme.label })),
3031];
3132
32export type MobileThemeVariable = `--color-${string}`;
33// Closed set: every key `createMobileThemeVariables` writes. Reads of a
34// misspelled variable then fail to compile instead of yielding undefined.
35export type MobileThemeVariable = keyof ReturnType<typeof createMobileThemeVariables>;
3336export type MobileThemeVariables = Readonly<Record<MobileThemeVariable, string>>;
3437
3538export function normalizeMobileThemeId(value: unknown): MobileThemeId {
......@@ -136,9 +139,9 @@ function withAlpha(color: string, alpha: number): string {
136139
137140function rgbChannels(color: string): readonly [number, number, number] | null {
138141 const match = /^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i.exec(color);
139 return match
140 ? [Number.parseInt(match[1], 16), Number.parseInt(match[2], 16), Number.parseInt(match[3], 16)]
141 : null;
142 if (!match) return null;
143 const [, red = "0", green = "0", blue = "0"] = match;
144 return [Number.parseInt(red, 16), Number.parseInt(green, 16), Number.parseInt(blue, 16)];
142145}
143146
144147/**
......@@ -216,18 +219,15 @@ function readableMessageAccent(accent: string, surface: string): string {
216219}
217220
218221export function themeColorWithAlpha(color: string, alpha: number): string {
219 const hex = /^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i.exec(color);
220 if (hex) {
221 return `rgba(${Number.parseInt(hex[1], 16)}, ${Number.parseInt(hex[2], 16)}, ${Number.parseInt(hex[3], 16)}, ${alpha})`;
222 const channels = rgbChannels(color);
223 if (channels) {
224 return `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${alpha})`;
222225 }
223226 const rgb = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/.exec(color);
224227 return rgb ? `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${alpha})` : color;
225228}
226229
227export function createMobileThemeVariables(
228 colors: ThemeColors,
229 appearance: MobileThemeAppearance,
230): MobileThemeVariables {
230export function createMobileThemeVariables(colors: ThemeColors, appearance: MobileThemeAppearance) {
231231 const c = nativeColors(colors);
232232 return {
233233 "--color-screen": c.canvas,
......@@ -315,7 +315,7 @@ export function createMobileThemeVariables(
315315}
316316
317317export const MOBILE_THEME_VARIABLE_NAMES = Object.keys(
318 createMobileThemeVariables(BUILT_IN_THEMES[0].colors, "light"),
318 createMobileThemeVariables(T3_CHAT_THEME.colors, "light"),
319319) as ReadonlyArray<MobileThemeVariable>;
320320
321321export function getMobileThemeVariables(
......@@ -323,7 +323,7 @@ export function getMobileThemeVariables(
323323 appearance: MobileThemeAppearance,
324324 overrides: Partial<MobileThemeVariables> | null = null,
325325): MobileThemeVariables {
326 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? BUILT_IN_THEMES[0];
326 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;
327327 const colors = getThemeColorsForAppearance(theme, appearance) ?? theme.colors;
328328 const baseVariables = createMobileThemeVariables(colors, appearance);
329329
......@@ -337,7 +337,7 @@ export function getMobileThemePreviewColors(
337337): ThemePreviewColors {
338338 if (themeId === DEFAULT_MOBILE_THEME_ID || themeId === "material-you")
339339 return STANDARD_THEME_PREVIEW_COLORS[appearance];
340 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? BUILT_IN_THEMES[0];
340 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;
341341 const colors = getThemeColorsForAppearance(theme, appearance) ?? theme.colors;
342342 return {
343343 canvas: themeColorToNativeColor(colors.canvas),
apps/mobile/src/lib/wideMarkdownBlocks.ts+5-1
......@@ -81,7 +81,11 @@ function hasOrderedListItem(text: string): boolean {
8181 const nestedMatch = INDENTED_ORDERED_LIST_ITEM.exec(line);
8282 const parentMatch =
8383 previousNonEmptyLine === null ? null : ANY_LIST_ITEM.exec(previousNonEmptyLine);
84 if (nestedMatch && parentMatch && parentMatch[1].length < nestedMatch[1].length) {
84 if (
85 nestedMatch?.[1] !== undefined &&
86 parentMatch?.[1] !== undefined &&
87 parentMatch[1].length < nestedMatch[1].length
88 ) {
8589 return true;
8690 }
8791
apps/mobile/tsconfig.json+3-1
......@@ -2,6 +2,8 @@
22 "extends": "expo/tsconfig.base",
33 "compilerOptions": {
44 "allowImportingTsExtensions": true,
5 "strict": true
5 "strict": true,
6 "noUncheckedIndexedAccess": true,
7 "noImplicitOverride": true
68 }
79}