fix(tui): scroll the /model Roles view so clipped rows stay reachable
The Roles panel renderer looped from the first row with a hard height cap and no scroll offset, so roles and model-keyed fallback chains past the visible height were never drawn and unreachable by keyboard, with no truncation indicator. Unlike the sibling provider list (model-browser windows via #windowStart/#ensureSelectedVisible), the Roles panel had no equivalent. Window #renderRolesView around #roleIndex via #ensureRoleVisible, offset mouse hit-testing by the scroll start bounded to the visible count, and draw an up/down '+N more' hint when the list is clipped. Fixes #8817
This commit is contained in:
@@ -92,12 +92,13 @@ function createHub(options: {
|
||||
registry?: RegistryOverrides;
|
||||
hub?: ModelHubOptions;
|
||||
callbacks?: Partial<ModelHubCallbacks>;
|
||||
terminalRows?: number;
|
||||
}): HubHarness {
|
||||
installTestTheme();
|
||||
const modelsFn = typeof options.models === "function" ? options.models : () => options.models as Model[];
|
||||
const settings = options.settings ?? Settings.isolated({});
|
||||
const registry = makeRegistry(modelsFn, options.registry);
|
||||
const ui = { requestRender: vi.fn(), terminal: { rows: 40 } } as unknown as TUI;
|
||||
const ui = { requestRender: vi.fn(), terminal: { rows: options.terminalRows ?? 40 } } as unknown as TUI;
|
||||
const onAssign = vi.fn();
|
||||
const onUnassign = vi.fn();
|
||||
const onLoginRequest = vi.fn();
|
||||
@@ -709,6 +710,35 @@ describe("ModelHub", () => {
|
||||
expect(onFallbackChainChange).toHaveBeenLastCalledWith("default", ["test/model-b"]);
|
||||
});
|
||||
|
||||
test("windows the roles list so model-keyed chains past the panel height stay reachable", () => {
|
||||
const settings = Settings.isolated({
|
||||
// Model-keyed chains sort alphabetically; the unique tail key lands last.
|
||||
"retry.fallbackChains": {
|
||||
"aa-provider/head-chain": ["x/y"],
|
||||
"mm-provider/mid-chain": ["x/y"],
|
||||
"zz-provider/tail-chain-marker": ["x/y"],
|
||||
},
|
||||
});
|
||||
// A short terminal makes the built-in roles alone fill the panel, so the
|
||||
// model-keyed chains that follow the separator land below the fold. The
|
||||
// chain keys are not available models, so no role auto-assignment leaks
|
||||
// their names into the visible role rows.
|
||||
const { hub } = createHub({ models: [makeModel("test", "solo")], settings, terminalRows: 16 });
|
||||
|
||||
enterRolesView(hub);
|
||||
const top = normalize(hub.render(120));
|
||||
// The alphabetically last model-keyed chain is clipped, but the panel
|
||||
// now advertises the hidden rows instead of dropping them silently.
|
||||
expect(top).not.toContain("tail-chain-marker");
|
||||
expect(top).toContain("more");
|
||||
|
||||
// Wrapping up from the top row lands on the trailing "+ New fallback…"
|
||||
// row; the window scrolls to the bottom and reveals the clipped chain.
|
||||
hub.handleInput(UP);
|
||||
const bottom = normalize(hub.render(120));
|
||||
expect(bottom).toContain("tail-chain-marker");
|
||||
});
|
||||
|
||||
test("clicking a roles row hits the row under the pointer", () => {
|
||||
const a = makeModel("test", "model-a");
|
||||
const { hub } = createHub({ models: [a], scoped: true });
|
||||
|
||||
Reference in New Issue
Block a user