feat(tui): persist model tier selection and fix formatting

Persist /model tier choice to ~/.local/share/flynn/preferences.json so
it survives restarts. Decode HTML entities (e.g. ') in markdown
renderer output. Suppress noisy logger.info and punycode deprecation
warnings in TUI startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-10 12:23:12 -08:00
parent 50471d63af
commit 411c6d84a2
8 changed files with 126 additions and 13 deletions
+6
View File
@@ -109,4 +109,10 @@ describe('renderMarkdown', () => {
expect(result).toContain('\x1b[34m');
expect(result).toContain('https://example.com');
});
it('decodes HTML entities like apostrophes', () => {
const result = renderMarkdown("I'm here. What's up?");
expect(result).toContain("I'm here");
expect(result).not.toContain('&#39;');
});
});
+13 -10
View File
@@ -27,14 +27,7 @@ function convertHtmlToTerminal(text: string): string {
// Horizontal rules
.replace(/<hr\s*\/?>/gi, '─'.repeat(40) + '\n')
// Strip any remaining HTML tags
.replace(/<[^>]+>/g, '')
// Decode common HTML entities
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&nbsp;/g, ' ');
.replace(/<[^>]+>/g, '');
}
// Custom renderer as an object for marked.use()
@@ -147,19 +140,29 @@ const terminalRenderer: RendererObject = {
},
html({ text }: Tokens.HTML | Tokens.Tag): string {
return convertHtmlToTerminal(text);
return decodeHtmlEntities(convertHtmlToTerminal(text));
},
};
// Configure marked with our renderer
marked.use({ renderer: terminalRenderer });
function decodeHtmlEntities(text: string): string {
return text
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&nbsp;/g, ' ');
}
export function renderMarkdown(text: string): string {
try {
const rendered = marked.parse(text);
// marked.parse can return string | Promise<string>, we only use sync
if (typeof rendered === 'string') {
return rendered.trim();
return decodeHtmlEntities(rendered).trim();
}
return text;
} catch {