mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-17 14:42:06 +00:00
Salvage of #45240. The dismiss-settled-tool-rows affordance was correct in intent but had two issues against current main: - The thread is virtualized, so a row's component unmounts/remounts as it scrolls. Component-local `useState` dismissal was forgotten on remount and the row popped back. Move dismissal into a session-scoped nanostore keyed by the stable disclosure id (mirrors $toolDisclosureOpen), so a dismissed row stays gone while scrolling but a reload restores real history instead of permanently rewriting it. - The dismiss button lived in DisclosureRow's absolute `trailing` slot — the exact "opacity-0-but-clickable control fights the caret" pattern the trailing comment warns against. Add an in-flow `action` slot that lays out at the far right so an interactive control never overlaps the caret's hit-target, regardless of title length, and move the dismiss button into it. Adds a remount regression test alongside the existing dismissal coverage.
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
import { atom, computed, type ReadableAtom } from 'nanostores'
|
|
|
|
type DismissedToolRows = Record<string, true>
|
|
|
|
// Tool rows the user has locally hidden via a row's dismiss control. This is a
|
|
// *view-only* hide: the underlying tool call still lives in the stored chat
|
|
// history, but once a turn has settled the user can clear a completed/failed
|
|
// row out of the way so it stops sitting at the tail of the conversation.
|
|
//
|
|
// Kept in module memory (not localStorage, unlike $toolDisclosureStates) on
|
|
// purpose: the thread is virtualized, so a dismissed row's component unmounts
|
|
// and remounts as it scrolls — component-local state would forget the dismissal
|
|
// and the row would pop back. Storing it here survives those remounts for the
|
|
// life of the app session, while a reload restores every row in place rather
|
|
// than permanently rewriting history from a stray click.
|
|
export const $dismissedToolRows = atom<DismissedToolRows>({})
|
|
|
|
const dismissedCache = new Map<string, ReadableAtom<boolean>>()
|
|
|
|
export function $toolRowDismissed(id: string): ReadableAtom<boolean> {
|
|
let cached = dismissedCache.get(id)
|
|
|
|
if (!cached) {
|
|
cached = computed($dismissedToolRows, rows => Boolean(rows[id]))
|
|
dismissedCache.set(id, cached)
|
|
}
|
|
|
|
return cached
|
|
}
|
|
|
|
export function dismissToolRow(id: string) {
|
|
if (!id || $dismissedToolRows.get()[id]) {
|
|
return
|
|
}
|
|
|
|
$dismissedToolRows.set({ ...$dismissedToolRows.get(), [id]: true })
|
|
}
|
|
|
|
export function clearDismissedToolRows() {
|
|
if (Object.keys($dismissedToolRows.get()).length === 0) {
|
|
return
|
|
}
|
|
|
|
$dismissedToolRows.set({})
|
|
}
|