Files
hermes-agent/apps/desktop/src/lib/text.ts
T
Brooklyn Nicholson 5a027a0081 refactor(desktop): give duplicated helpers one owner
Splitting the god files made a pile of copy-paste helpers visible and,
for the first time, fixable — sharing them previously meant importing a
god file. Hashing function bodies through the TypeScript AST found
twelve groups desktop-wide; production code is now at zero duplicates.

Each helper went to the module that already owns its concern:
firstStringField to lib/text, the two REST 404 predicates to
lib/gateway-rpc beside isMissingRpcMethod, useDebounced and
prefersReducedMotion to their hooks, the superseded-bootstrap guard to
electron/ssh-connection, the composer keyup handler to the trigger hook
that owns the rest of that state machine, and clampDataUrlReadMaxMb to
apps/shared, replacing a "keep these in sync" comment between two
copies.

Only helpers with no existing owner got a new file: lib/mcp-servers,
lib/audio-context, lib/keyed-timeouts, lib/pointer-drag, and the command
palette's status row. Error-shape predicates are the worst thing to
copy — when the backend changes how it reports a missing route, every
copy has to be found.
2026-08-19 10:50:52 -05:00

30 lines
1.2 KiB
TypeScript

// Canonical text micro-helpers. Do not redefine these per-page.
export const asText = (v: unknown): string => (typeof v === 'string' ? v : v == null ? '' : String(v))
export const includesQuery = (v: unknown, q: string) => asText(v).toLowerCase().includes(q)
export const prettyName = (v: string) => v.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
/** Search-key normalization: the exact `value.trim().toLowerCase()` idiom that
* was hand-written at ~30 filter/lookup sites. */
export const normalize = (v: unknown): string => asText(v).trim().toLowerCase()
/** Uppercase the first character, leave the rest. Matches the
* `s.charAt(0).toUpperCase() + s.slice(1)` idiom (empty-safe). */
export const capitalize = (v: string): string => (v ? v.charAt(0).toUpperCase() + v.slice(1) : v)
/** First non-empty string among `keys`, trimmed. For reading tool args and
* results, where the key carrying the interesting value varies by tool. */
export const firstStringField = (record: Record<string, unknown>, keys: readonly string[]): string => {
for (const key of keys) {
const value = record[key]
if (typeof value === 'string' && value.trim()) {
return value.trim()
}
}
return ''
}