Hoist turn state from a 286-line hook into $turnState atom + turnController
singleton. createGatewayEventHandler becomes a typed dispatch over the
controller; its ctx shrinks from 30 fields to 5. Event-handler refs and 16
threaded actions are gone.
Fold three createSlash*Handler factories into a data-driven SlashCommand[]
registry under slash/commands/{core,session,ops}.ts. Aliases are data;
findSlashCommand does name+alias lookup. Shared guarded/guardedErr combinator
in slash/guarded.ts.
Split constants.ts + app/helpers.ts into config/ (timing/limits/env),
content/ (faces/placeholders/hotkeys/verbs/charms/fortunes), domain/ (roles/
details/messages/paths/slash/viewport/usage), protocol/ (interpolation/paste).
Type every RPC response in gatewayTypes.ts (26 new interfaces); drop all
`(r: any)` across slash + main app.
Shrink useMainApp from 1216 -> 646 lines by extracting useSessionLifecycle,
useSubmission, useConfigSync. Add <Fg> themed primitive and strip ~50
`as any` color casts.
Tests: 50 passing. Build + type-check clean.
45 lines
982 B
TypeScript
45 lines
982 B
TypeScript
import type { Msg } from '../types.js'
|
|
|
|
import { userDisplay } from './messages.js'
|
|
|
|
const upperBound = (offsets: ArrayLike<number>, target: number) => {
|
|
let lo = 0
|
|
let hi = offsets.length
|
|
|
|
while (lo < hi) {
|
|
const mid = (lo + hi) >> 1
|
|
|
|
offsets[mid]! <= target ? (lo = mid + 1) : (hi = mid)
|
|
}
|
|
|
|
return lo
|
|
}
|
|
|
|
export const stickyPromptFromViewport = (
|
|
messages: readonly Msg[],
|
|
offsets: ArrayLike<number>,
|
|
top: number,
|
|
sticky: boolean
|
|
) => {
|
|
if (sticky || !messages.length) {
|
|
return ''
|
|
}
|
|
|
|
const first = Math.max(0, Math.min(messages.length - 1, upperBound(offsets, top) - 1))
|
|
const aboveViewport = (i: number) => (offsets[i] ?? 0) + 1 < top
|
|
|
|
if (messages[first]?.role === 'user' && !aboveViewport(first)) {
|
|
return ''
|
|
}
|
|
|
|
for (let i = first - 1; i >= 0; i--) {
|
|
if (messages[i]?.role !== 'user' || !aboveViewport(i)) {
|
|
continue
|
|
}
|
|
|
|
return userDisplay(messages[i]!.text.trim()).replace(/\s+/g, ' ').trim()
|
|
}
|
|
|
|
return ''
|
|
}
|