Centralize frontmatter parsing + parse frontmatter with yaml library (#728)

* Add frontmatter utility and tidy coding agent prompts

* Add frontmatter parsing utilities and tests

* Parse frontmatter with YAML parser

* Simplify frontmatter parsing utilities

* strip body in 1 place

* Improve frontmatter parsing error handling

* Normalize multiline skill and select-list descriptions
This commit is contained in:
Richard Gill 2026-01-15 23:31:53 +00:00 committed by GitHub
parent df58d3191e
commit ce7e73b503
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 213 additions and 126 deletions

View file

@ -2,6 +2,8 @@ import { getEditorKeybindings } from "../keybindings.js";
import type { Component } from "../tui.js";
import { truncateToWidth } from "../utils.js";
const normalizeToSingleLine = (text: string): string => text.replace(/[\r\n]+/g, " ").trim();
export interface SelectItem {
value: string;
label: string;
@ -70,6 +72,7 @@ export class SelectList implements Component {
if (!item) continue;
const isSelected = i === this.selectedIndex;
const descriptionSingleLine = item.description ? normalizeToSingleLine(item.description) : undefined;
let line = "";
if (isSelected) {
@ -77,7 +80,7 @@ export class SelectList implements Component {
const prefixWidth = 2; // "→ " is 2 characters visually
const displayValue = item.label || item.value;
if (item.description && width > 40) {
if (descriptionSingleLine && width > 40) {
// Calculate how much space we have for value + description
const maxValueWidth = Math.min(30, width - prefixWidth - 4);
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
@ -88,7 +91,7 @@ export class SelectList implements Component {
const remainingWidth = width - descriptionStart - 2; // -2 for safety
if (remainingWidth > 10) {
const truncatedDesc = truncateToWidth(item.description, remainingWidth, "");
const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
// Apply selectedText to entire line content
line = this.theme.selectedText(`${truncatedValue}${spacing}${truncatedDesc}`);
} else {
@ -105,7 +108,7 @@ export class SelectList implements Component {
const displayValue = item.label || item.value;
const prefix = " ";
if (item.description && width > 40) {
if (descriptionSingleLine && width > 40) {
// Calculate how much space we have for value + description
const maxValueWidth = Math.min(30, width - prefix.length - 4);
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
@ -116,7 +119,7 @@ export class SelectList implements Component {
const remainingWidth = width - descriptionStart - 2; // -2 for safety
if (remainingWidth > 10) {
const truncatedDesc = truncateToWidth(item.description, remainingWidth, "");
const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
const descText = this.theme.description(spacing + truncatedDesc);
line = prefix + truncatedValue + descText;
} else {

View file

@ -0,0 +1,30 @@
import assert from "node:assert";
import { describe, it } from "node:test";
import { SelectList } from "../src/components/select-list.js";
const testTheme = {
selectedPrefix: (text: string) => text,
selectedText: (text: string) => text,
description: (text: string) => text,
scrollInfo: (text: string) => text,
noMatch: (text: string) => text,
};
describe("SelectList", () => {
it("normalizes multiline descriptions to single line", () => {
const items = [
{
value: "test",
label: "test",
description: "Line one\nLine two\nLine three",
},
];
const list = new SelectList(items, 5, testTheme);
const rendered = list.render(100);
assert.ok(rendered.length > 0);
assert.ok(!rendered[0].includes("\n"));
assert.ok(rendered[0].includes("Line one Line two Line three"));
});
});