Skip to content

Commit 1d598f0

Browse files
committed
evalbuff: add patterns/template-literal-escaping.md (6d8bf39)
1 parent a5b5a2b commit 1d598f0

File tree

3 files changed

+281
-0
lines changed

3 files changed

+281
-0
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,5 @@ Make an efficient learning agent that can do anything.
4444
- [`docs/patterns/handle-steps-generators.md`](docs/patterns/handle-steps-generators.md) — handleSteps generator patterns and spawn_agents tool calls
4545
- [docs/evalbuff/interpreting-task-prompts.md](docs/evalbuff/interpreting-task-prompts.md)
4646
- [docs/patterns/task-completion-validation.md](docs/patterns/task-completion-validation.md)
47+
- [docs/patterns/terminal-alternate-screen-buffer.md](docs/patterns/terminal-alternate-screen-buffer.md)
48+
- [docs/patterns/template-literal-escaping.md](docs/patterns/template-literal-escaping.md)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Template Literal Escaping Pattern
2+
3+
When modifying JavaScript/TypeScript code that contains template literals (backtick strings), always escape backticks that appear within the template literal content to prevent syntax errors.
4+
5+
## The Problem
6+
7+
Template literals use backticks (`) as delimiters. When you have backticks inside the template literal content, they must be escaped or they will break the JavaScript syntax.
8+
9+
**WRONG:**
10+
```typescript
11+
const message = `Use \`wait-idle\` with send** (e.g., `--wait-idle 3`) to wait for output`
12+
// ^ unescaped backtick breaks syntax
13+
```
14+
15+
**CORRECT:**
16+
```typescript
17+
const message = `Use \`wait-idle\` with send** (e.g., \`--wait-idle 3\`) to wait for output`
18+
// ^ properly escaped backticks
19+
```
20+
21+
## When This Happens
22+
23+
This issue commonly occurs when:
24+
- Modifying documentation strings that contain code examples with backticks
25+
- Updating help text or error messages that reference command-line syntax
26+
- Changing template literals that contain markdown-style code formatting
27+
- Replacing text that includes shell command examples
28+
29+
## The Fix
30+
31+
When working inside template literals, escape all backticks in the content:
32+
33+
1. **Find all backticks** in the string content (not the template literal delimiters)
34+
2. **Escape each one** with a backslash: `` ` `` becomes `` \` ``
35+
3. **Verify the syntax** - the opening and closing backticks of the template literal should be the only unescaped ones
36+
37+
## Examples from Real Code
38+
39+
### Helper Script Documentation
40+
```typescript
41+
// WRONG - breaks compilation
42+
const helperScript = `
43+
echo "Commands: send, capture, wait-idle"
44+
# Usage example: helper wait-idle "session" 3
45+
echo "Use \`--wait-idle 3\` for timing"
46+
`
47+
48+
// CORRECT - properly escaped
49+
const helperScript = `
50+
echo "Commands: send, capture, wait-idle"
51+
# Usage example: helper wait-idle "session" 3
52+
echo "Use \\\`--wait-idle 3\\\` for timing"
53+
`
54+
```
55+
56+
### Quick Reference Strings
57+
```typescript
58+
// WRONG
59+
const quickRef =
60+
'- Send + wait: `' + helperPath + ' send "' + sessionName + '" "..." --wait-idle 3`\n' +
61+
'- Example usage: `--wait-idle 3` waits for output\n'
62+
// ^ unescaped backticks in concatenated string
63+
64+
// CORRECT
65+
const quickRef =
66+
'- Send + wait: `' + helperPath + ' send "' + sessionName + '" "..." --wait-idle 3`\n' +
67+
'- Example usage: \`--wait-idle 3\` waits for output\n'
68+
// ^ properly escaped backticks
69+
```
70+
71+
## Detection
72+
73+
Syntax errors from unescaped backticks typically show:
74+
- `TS1005: ',' expected` or `TS1005: ';' expected`
75+
- `TS1003: Identifier expected`
76+
- `error: Expected "}" but found "<word>"`
77+
- Compilation errors pointing to the line with unescaped backticks
78+
79+
## Prevention
80+
81+
1. **When modifying template literals**, scan for all backticks in the content
82+
2. **Use find-and-replace** to systematically escape backticks: find `` ` `` replace with `` \` ``
83+
3. **Test compilation** after making changes to catch syntax errors early
84+
4. **Be extra careful** with documentation strings, help text, and code examples
85+
86+
## Key Rule
87+
88+
Inside template literals, the only unescaped backticks should be the opening and closing delimiters of the template literal itself. All backticks in the content must be escaped with backslashes.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Terminal Alternate Screen Buffer Pattern
2+
3+
When building CLI applications with full-screen UIs (like TUI apps), use the alternate screen buffer to prevent UI output from polluting the user's terminal scrollback when the app exits.
4+
5+
## The Problem
6+
7+
By default, terminal applications write to the main screen buffer. When a full-screen CLI app exits, all its UI output remains in the terminal scrollback, cluttering the user's terminal history. This is annoying for users who expect clean terminal behavior like vim, less, htop, and other well-behaved CLI tools.
8+
9+
## The Solution: Alternate Screen Buffer
10+
11+
Terminals support an alternate screen buffer that can be entered/exited using ANSI escape sequences:
12+
13+
- **Enter alternate screen:** `\x1b[?1049h` (smcup)
14+
- **Exit alternate screen:** `\x1b[?1049l` (rmcup)
15+
16+
When you enter the alternate screen buffer, the terminal saves the current screen content. When you exit, it restores the original content, leaving the scrollback clean.
17+
18+
## Implementation Pattern
19+
20+
### 1. Define the Escape Sequences
21+
22+
```typescript
23+
// Terminal alternate screen buffer escape sequences
24+
export const ENTER_ALT_BUFFER = '\x1b[?1049h'
25+
export const EXIT_ALT_BUFFER = '\x1b[?1049l'
26+
```
27+
28+
### 2. Enter Before Rendering
29+
30+
Enter the alternate screen buffer BEFORE initializing your UI renderer:
31+
32+
```typescript
33+
export function enterAlternateScreen(): void {
34+
if (process.stdout.isTTY) {
35+
process.stdout.write(ENTER_ALT_BUFFER)
36+
}
37+
}
38+
39+
async function main(): Promise<void> {
40+
// Enter alternate screen buffer BEFORE rendering the app
41+
if (process.stdout.isTTY) {
42+
enterAlternateScreen()
43+
}
44+
45+
// Initialize your UI renderer after entering alternate buffer
46+
const renderer = await createCliRenderer({ ... })
47+
// ... rest of app initialization
48+
}
49+
```
50+
51+
### 3. Exit During Cleanup
52+
53+
Ensure the alternate screen buffer is exited during all cleanup scenarios:
54+
55+
```typescript
56+
const TERMINAL_RESET_SEQUENCES =
57+
EXIT_ALT_BUFFER + // Exit alternate screen buffer (restores main screen)
58+
'\x1b[?1000l' + // Disable X10 mouse mode
59+
'\x1b[?1002l' + // Disable button event mouse mode
60+
// ... other terminal reset sequences
61+
'\x1b[?25h' // Show cursor
62+
63+
function resetTerminalState(): void {
64+
try {
65+
process.stdout.write(TERMINAL_RESET_SEQUENCES)
66+
} catch {
67+
// Ignore errors - stdout may already be closed
68+
}
69+
}
70+
```
71+
72+
### 4. Handle All Exit Scenarios
73+
74+
Register cleanup handlers for all possible exit scenarios:
75+
76+
```typescript
77+
process.on('SIGTERM', cleanup)
78+
process.on('SIGHUP', cleanup)
79+
process.on('SIGINT', cleanup)
80+
process.on('beforeExit', cleanup)
81+
process.on('exit', cleanup)
82+
process.on('uncaughtException', cleanup)
83+
process.on('unhandledRejection', cleanup)
84+
```
85+
86+
## Key Considerations
87+
88+
### TTY Detection
89+
90+
Only enter alternate screen buffer in interactive terminals:
91+
92+
```typescript
93+
if (process.stdout.isTTY) {
94+
enterAlternateScreen()
95+
}
96+
```
97+
98+
This prevents issues when:
99+
- Output is piped to a file (`app > output.txt`)
100+
- Running in CI/automated environments
101+
- Output is redirected or captured
102+
103+
### Timing is Critical
104+
105+
1. **Enter alternate buffer FIRST** - before any UI initialization
106+
2. **Exit alternate buffer LAST** - as part of terminal reset sequences
107+
3. **Write exit sequence directly to stdout** - don't rely on UI renderer cleanup
108+
109+
### Terminal Compatibility
110+
111+
The `?1049` sequence is widely supported by modern terminals:
112+
- xterm, gnome-terminal, iTerm2, Terminal.app
113+
- tmux, screen (with proper configuration)
114+
- Windows Terminal, ConEmu
115+
116+
Very old terminals may not support it, but the TTY check provides a reasonable fallback.
117+
118+
## Integration with UI Frameworks
119+
120+
### OpenTUI Example
121+
122+
```typescript
123+
import { createCliRenderer } from '@opentui/core'
124+
125+
async function main(): Promise<void> {
126+
// Enter alternate screen BEFORE creating renderer
127+
if (process.stdout.isTTY) {
128+
enterAlternateScreen()
129+
}
130+
131+
const renderer = await createCliRenderer({
132+
backgroundColor: 'transparent',
133+
exitOnCtrlC: false,
134+
})
135+
136+
// Install cleanup handlers that exit alternate screen
137+
installProcessCleanupHandlers(renderer)
138+
139+
// Render your app
140+
createRoot(renderer).render(<App />)
141+
}
142+
```
143+
144+
### Ink.js Example
145+
146+
```typescript
147+
import { render } from 'ink'
148+
149+
function main() {
150+
if (process.stdout.isTTY) {
151+
enterAlternateScreen()
152+
}
153+
154+
const { unmount } = render(<App />)
155+
156+
// Ensure cleanup on exit
157+
process.on('exit', () => {
158+
unmount()
159+
resetTerminalState()
160+
})
161+
}
162+
```
163+
164+
## Testing
165+
166+
To verify alternate screen buffer works correctly:
167+
168+
1. **Before running your CLI:** Note some text in your terminal scrollback
169+
2. **Run your CLI:** The UI should appear in a clean screen
170+
3. **Exit your CLI:** You should return to the exact terminal state from step 1
171+
4. **Check scrollback:** The UI output should not appear in your scrollback history
172+
173+
## Common Mistakes
174+
175+
**Entering alternate buffer too late** - after UI initialization
176+
**Not checking TTY status** - breaks piped output
177+
**Forgetting exit sequences** - leaves terminal in alternate buffer
178+
**Not handling all exit scenarios** - cleanup only works for normal exit
179+
**Relying on UI framework cleanup** - may not run if framework crashes
180+
181+
## When to Use
182+
183+
Use alternate screen buffer for:
184+
- Full-screen TUI applications
185+
- Interactive CLI tools with complex UIs
186+
- Any CLI that renders multiple lines of output that users don't need to reference later
187+
188+
Don't use for:
189+
- Simple command-line tools with minimal output
190+
- Tools where users need to reference output after exit
191+
- Log viewers or tools that should integrate with terminal scrollback

0 commit comments

Comments
 (0)