Skip to content

Commit a3457b4

Browse files
committed
revert: reset non-billing files back to main
1 parent 6b294b2 commit a3457b4

File tree

8 files changed

+95
-93
lines changed

8 files changed

+95
-93
lines changed

cli/src/components/clickable.tsx

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,20 @@ import React, { cloneElement, isValidElement, memo } from 'react'
22
import type { ReactElement, ReactNode } from 'react'
33

44
/**
5-
* Makes all `<text>` and `<span>` children non-selectable.
6-
* Use for interactive elements where text selection during clicks is undesirable.
5+
* Makes all text content within a React node tree non-selectable.
6+
*
7+
* This is important for interactive elements (buttons, clickable boxes) because
8+
* text inside them should not be selectable when the user clicks - it creates
9+
* a poor UX where text gets highlighted during interactions.
10+
*
11+
* Handles both `<text>` and `<span>` OpenTUI elements by adding `selectable={false}`.
12+
*
13+
* @example
14+
* ```tsx
15+
* // Use this when building custom interactive components
16+
* const processedChildren = makeTextUnselectable(children)
17+
* return <box onMouseDown={handleClick}>{processedChildren}</box>
18+
* ```
719
*/
820
export function makeTextUnselectable(node: ReactNode): ReactNode {
921
if (node === null || node === undefined || typeof node === 'boolean') return node
@@ -44,8 +56,34 @@ interface ClickableProps {
4456
}
4557

4658
/**
47-
* Wrapper for interactive areas. Makes text non-selectable automatically.
48-
* Use `as="text"` for inline clickable text, default is `as="box"`.
59+
* A wrapper component for any interactive/clickable area in the CLI.
60+
*
61+
* **Why use this instead of raw `<box>` or `<text>` with mouse handlers?**
62+
*
63+
* This component automatically makes all text content non-selectable, which is
64+
* essential for good UX - users shouldn't accidentally select text when clicking
65+
* interactive elements.
66+
*
67+
* **The `as` prop:**
68+
* - `as="box"` (default) - Renders a `<box>` element for layout containers
69+
* - `as="text"` - Renders a `<text>` element for inline clickable text
70+
*
71+
* **When to use `Clickable` vs `Button`:**
72+
* - Use `Button` for actual button-like interactions (has click-on-mouseup logic)
73+
* - Use `Clickable` for simpler interactive areas where you need direct mouse event control
74+
*
75+
* @example
76+
* ```tsx
77+
* // Default: renders <box>
78+
* <Clickable onMouseDown={handleClick}>
79+
* <text>Click me</text>
80+
* </Clickable>
81+
*
82+
* // For inline text: renders <text>
83+
* <Clickable as="text" onMouseDown={handleCopy}>
84+
* <span>⎘ copy</span>
85+
* </Clickable>
86+
* ```
4987
*/
5088
export const Clickable = memo(function Clickable({
5189
as = 'box',

common/src/util/__tests__/promise.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ describe('withRetry', () => {
299299

300300
const result = await withRetry(operation, {
301301
maxRetries: 3,
302-
retryIf: (error) => (error as { code?: string })?.code === 'RETRY_ME',
302+
retryIf: (error) => error?.code === 'RETRY_ME',
303303
})
304304

305305
expect(result).toBe('success')
@@ -315,7 +315,7 @@ describe('withRetry', () => {
315315
await expect(
316316
withRetry(operation, {
317317
maxRetries: 3,
318-
retryIf: (err) => (err as { code?: string })?.code === 'RETRY_ME',
318+
retryIf: (err) => err?.code === 'RETRY_ME',
319319
}),
320320
).rejects.toMatchObject({ code: 'DO_NOT_RETRY' })
321321

common/src/util/__tests__/split-data.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('splitData - base cases', () => {
2121
it('splits short strings when maxChunkSize is small', () => {
2222
const input = { msg: 'abcdef'.repeat(10) } // 60 chars
2323

24-
const chunks = splitData({ data: input, maxChunkSize: 30 }) as { msg?: string }[]
24+
const chunks = splitData({ data: input, maxChunkSize: 30 })
2525

2626
expect(chunks.length).toBeGreaterThan(1)
2727
const combined = chunks.map((c) => c.msg).join('')
@@ -32,7 +32,7 @@ describe('splitData - base cases', () => {
3232
it('splits deeply nested strings with small maxChunkSize', () => {
3333
const input = { a: { b: { c: 'xyz123'.repeat(10) } } }
3434

35-
const chunks = splitData({ data: input, maxChunkSize: 50 }) as { a?: { b?: { c?: string } } }[]
35+
const chunks = splitData({ data: input, maxChunkSize: 50 })
3636

3737
expect(chunks.length).toBeGreaterThan(1)
3838
const reconstructed = chunks.map((c) => c.a?.b?.c ?? '').join('')
@@ -60,7 +60,7 @@ describe('splitData - base cases', () => {
6060
str: 'hello world'.repeat(5),
6161
}
6262

63-
const chunks = splitData({ data: input, maxChunkSize: 50 }) as { flag?: boolean; num?: number; str?: string }[]
63+
const chunks = splitData({ data: input, maxChunkSize: 50 })
6464

6565
expect(chunks.length).toBeGreaterThan(1)
6666
expect(chunks.every((c) => JSON.stringify(c).length <= 50)).toBe(true)
@@ -74,7 +74,7 @@ describe('splitData - base cases', () => {
7474
a: 'A'.repeat(20),
7575
b: 'B'.repeat(20),
7676
}
77-
const chunks = splitData({ data: input, maxChunkSize: 30 }) as { a?: string; b?: string }[]
77+
const chunks = splitData({ data: input, maxChunkSize: 30 })
7878

7979
expect(chunks.length).toBeGreaterThan(1)
8080

@@ -89,7 +89,7 @@ describe('splitData - base cases', () => {
8989
describe('splitData - array and string-specific splitting', () => {
9090
it('splits long strings into smaller string chunks', () => {
9191
const input = '12345678901234567890'
92-
const chunks = splitData({ data: input, maxChunkSize: 5 }) as string[]
92+
const chunks = splitData({ data: input, maxChunkSize: 5 })
9393

9494
expect(Array.isArray(chunks)).toBe(true)
9595
chunks.forEach((chunk) => {
@@ -121,7 +121,7 @@ describe('splitData - array and string-specific splitting', () => {
121121
b: 'bbb'.repeat(10),
122122
}
123123
const maxSize = 40
124-
const chunks = splitData({ data: input, maxChunkSize: maxSize }) as { a?: string; b?: string }[]
124+
const chunks = splitData({ data: input, maxChunkSize: maxSize })
125125

126126
expect(Array.isArray(chunks)).toBe(true)
127127
chunks.forEach((chunk) => {
@@ -162,7 +162,7 @@ describe('splitData - array and string-specific splitting', () => {
162162
]
163163
const maxSize = 30
164164

165-
const chunks = splitData({ data: input, maxChunkSize: maxSize }) as { msg?: string; val?: number }[][]
165+
const chunks = splitData({ data: input, maxChunkSize: maxSize })
166166

167167
expect(Array.isArray(chunks)).toBe(true)
168168
chunks.forEach((chunk) => {

common/src/util/error.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,29 @@ export function success<T>(value: T): Success<T> {
3333
}
3434
}
3535

36-
export function failure(error: unknown): Failure<ErrorObject> {
36+
export function failure(error: any): Failure<ErrorObject> {
3737
return {
3838
success: false,
3939
error: getErrorObject(error),
4040
}
4141
}
4242

4343
export function getErrorObject(
44-
error: unknown,
44+
error: any,
4545
options: { includeRawError?: boolean } = {},
4646
): ErrorObject {
4747
if (error instanceof Error) {
48-
const errorWithExtras = error as { status?: unknown; statusCode?: unknown; code?: unknown }
48+
const anyError = error as any
4949
return {
5050
name: error.name,
5151
message: error.message,
5252
stack: error.stack,
53-
status: typeof errorWithExtras.status === 'number' ? errorWithExtras.status : undefined,
53+
status: typeof anyError.status === 'number' ? anyError.status : undefined,
5454
statusCode:
55-
typeof errorWithExtras.statusCode === 'number'
56-
? errorWithExtras.statusCode
55+
typeof anyError.statusCode === 'number'
56+
? anyError.statusCode
5757
: undefined,
58-
code: typeof errorWithExtras.code === 'string' ? errorWithExtras.code : undefined,
58+
code: typeof anyError.code === 'string' ? anyError.code : undefined,
5959
rawError: options.includeRawError
6060
? JSON.stringify(error, null, 2)
6161
: undefined,

common/src/util/object.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,41 @@
11
import { isEqual, mapValues, union } from 'lodash'
22

3-
type RemoveUndefined<T extends object> = {
4-
[K in keyof T as T[K] extends undefined ? never : K]: Exclude<T[K], undefined>
5-
}
6-
73
export const removeUndefinedProps = <T extends object>(
84
obj: T,
9-
): RemoveUndefined<T> => {
10-
const newObj: Record<string, unknown> = {}
5+
): {
6+
[K in keyof T as T[K] extends undefined ? never : K]: Exclude<T[K], undefined>
7+
} => {
8+
const newObj: any = {}
119

1210
for (const key of Object.keys(obj)) {
13-
const value = obj[key as keyof T]
14-
if (value !== undefined) newObj[key] = value
11+
if ((obj as any)[key] !== undefined) newObj[key] = (obj as any)[key]
1512
}
1613

17-
return newObj as RemoveUndefined<T>
14+
return newObj
1815
}
1916

2017
export const removeNullOrUndefinedProps = <T extends object>(
2118
obj: T,
2219
exceptions?: string[],
2320
): T => {
24-
const newObj: Record<string, unknown> = {}
21+
const newObj: any = {}
2522

2623
for (const key of Object.keys(obj)) {
27-
const value = obj[key as keyof T]
2824
if (
29-
(value !== undefined && value !== null) ||
25+
((obj as any)[key] !== undefined && (obj as any)[key] !== null) ||
3026
(exceptions ?? []).includes(key)
3127
)
32-
newObj[key] = value
28+
newObj[key] = (obj as any)[key]
3329
}
34-
return newObj as T
30+
return newObj
3531
}
3632

3733
export const addObjects = <T extends { [key: string]: number }>(
3834
obj1: T,
3935
obj2: T,
4036
) => {
4137
const keys = union(Object.keys(obj1), Object.keys(obj2))
42-
const newObj: Record<string, number> = {}
38+
const newObj = {} as any
4339

4440
for (const key of keys) {
4541
newObj[key] = (obj1[key] ?? 0) + (obj2[key] ?? 0)
@@ -53,7 +49,7 @@ export const subtractObjects = <T extends { [key: string]: number }>(
5349
obj2: T,
5450
) => {
5551
const keys = union(Object.keys(obj1), Object.keys(obj2))
56-
const newObj: Record<string, number> = {}
52+
const newObj = {} as any
5753

5854
for (const key of keys) {
5955
newObj[key] = (obj1[key] ?? 0) - (obj2[key] ?? 0)
@@ -72,19 +68,14 @@ export const hasSignificantDeepChanges = <T extends object>(
7268
partial: Partial<T>,
7369
epsilonForNumbers: number,
7470
): boolean => {
75-
const compareValues = (currValue: unknown, partialValue: unknown): boolean => {
71+
const compareValues = (currValue: any, partialValue: any): boolean => {
7672
if (typeof currValue === 'number' && typeof partialValue === 'number') {
7773
return Math.abs(currValue - partialValue) > epsilonForNumbers
7874
}
79-
if (
80-
typeof currValue === 'object' &&
81-
currValue !== null &&
82-
typeof partialValue === 'object' &&
83-
partialValue !== null
84-
) {
75+
if (typeof currValue === 'object' && typeof partialValue === 'object') {
8576
return hasSignificantDeepChanges(
86-
currValue as Record<string, unknown>,
87-
partialValue as Partial<Record<string, unknown>>,
77+
currValue,
78+
partialValue,
8879
epsilonForNumbers,
8980
)
9081
}
@@ -104,7 +95,7 @@ export const hasSignificantDeepChanges = <T extends object>(
10495

10596
export const filterObject = <T extends object>(
10697
obj: T,
107-
predicate: (value: T[keyof T], key: keyof T) => boolean,
98+
predicate: (value: any, key: keyof T) => boolean,
10899
): { [P in keyof T]: T[P] } => {
109100
const result = {} as { [P in keyof T]: T[P] }
110101
for (const key in obj) {

common/src/util/promise.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,19 @@ export async function withRetry<T>(
44
operation: () => Promise<T>,
55
options: {
66
maxRetries?: number
7-
retryIf?: (error: unknown) => boolean
8-
onRetry?: (error: unknown, attempt: number) => void
7+
retryIf?: (error: any) => boolean
8+
onRetry?: (error: any, attempt: number) => void
99
retryDelayMs?: number
1010
} = {},
1111
): Promise<T> {
1212
const {
1313
maxRetries = 3,
14-
retryIf = (error) => {
15-
const errorObj = error as { type?: string } | null | undefined
16-
return errorObj?.type === 'APIConnectionError'
17-
},
14+
retryIf = (error) => error?.type === 'APIConnectionError',
1815
onRetry = () => {},
1916
retryDelayMs = INITIAL_RETRY_DELAY,
2017
} = options
2118

22-
let lastError: unknown = null
19+
let lastError: any = null
2320

2421
for (let attempt = 0; attempt < maxRetries; attempt++) {
2522
try {

0 commit comments

Comments
 (0)