Gitignore (core change): - Remove stale blanket .sf/ entries from .gitignore (migrated to .git/info/exclude on 2026-04-29, never cleaned up) - gitignore.ts: split SF_RUNTIME_EXCLUSION_PATTERNS into two modes — SF_SYMLINK_EXCLUSION_PATTERNS (blanket .sf for symlink repos where git cannot traverse the symlink) and SF_RUNTIME_EXCLUSION_PATTERNS (granular runtime-only patterns for directory repos, enabling .sf/milestones/ and other durable planning artifacts to be tracked) - ensureGitInfoExclude() now detects symlink vs directory and writes the correct patterns, handling transitions between modes cleanly - ADR-001 status: Proposed → Accepted Docs: - Fill 11 placeholder scaffold docs with real SF-specific content: PLANS, DESIGN, PRODUCT_SENSE, QUALITY_SCORE, RELIABILITY, SECURITY, design-docs/index.md, exec-plans/active, exec-plans/completed, exec-plans/tech-debt-tracker, records/index - Add records note: docs/records/2026-05-01-repo-vcs-and-notifications.md - ADR-008 status: Accepted → Proposed (deferred — not applicable to current usage model where Claude Code assists externally, not as a Pi provider inside SF's dispatch loop) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
56 lines
2.6 KiB
Markdown
56 lines
2.6 KiB
Markdown
# Design
|
|
|
|
SF's UI is a terminal application built on the Pi TUI framework (`@mariozechner/pi-tui`). These are the binding constraints any UI work must respect.
|
|
|
|
## The Cardinal Rule: Line Width
|
|
|
|
**Every line returned from `render(width)` must not exceed `width` in visible characters.** Exceeding it causes terminal line-wrapping, cursor misposition, and visual corruption the framework cannot fix.
|
|
|
|
Use the Pi TUI utilities — never raw `string.length`:
|
|
|
|
```typescript
|
|
import { visibleWidth, truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
|
|
visibleWidth("\x1b[32mHello\x1b[0m"); // 5, not 14
|
|
truncateToWidth("Very long text here", 10); // "Very lo..."
|
|
wrapTextWithAnsi("\x1b[32mlong green\x1b[0m", 15); // preserves ANSI per line
|
|
```
|
|
|
|
`visibleWidth` strips ANSI escape codes before measuring. `truncateToWidth` preserves ANSI codes in the truncated output. Use these everywhere a line's display length matters.
|
|
|
|
## Render Pattern
|
|
|
|
```typescript
|
|
render(width: number): string[] {
|
|
const lines: string[] = [];
|
|
lines.push(truncateToWidth(` ${prefix}${content}`, width));
|
|
|
|
const labelWidth = visibleWidth(label);
|
|
const available = width - labelWidth - 4; // padding
|
|
lines.push(` ${label}: ${truncateToWidth(value, available)}`);
|
|
|
|
return lines;
|
|
}
|
|
```
|
|
|
|
## Overlays and Modals
|
|
|
|
Floating panels use the Pi TUI overlay pattern: they render at a fixed position within the terminal bounds and must still respect the outer `width` constraint. An overlay that overflows its bounds causes the same wrapping corruption as any other component.
|
|
|
|
Use `ctx.ui.dialog()` for modal user input. Use `ctx.ui.notify()` for transient non-blocking notices. Persistent notification state goes through `notification-store.ts` → `notification-overlay.ts`.
|
|
|
|
## Theming
|
|
|
|
Colors and styles come from the Pi TUI theme system, not from hardcoded ANSI codes. Access the active theme via the `ExtensionContext`. Respect theme changes: components must re-render when the theme changes (implement `onThemeChange` if caching rendered output).
|
|
|
|
## IME and Focus
|
|
|
|
Interactive input components must implement the `Focusable` interface to receive keyboard events correctly, especially for IME (input method editor) support on non-ASCII keyboards. Components that handle key input but do not implement `Focusable` will silently swallow events.
|
|
|
|
## Performance
|
|
|
|
Cache rendered output when the underlying data hasn't changed. Invalidate the cache on data change or theme change. Do not re-render on every tick. The TUI framework calls `render()` frequently; rendering must be cheap.
|
|
|
|
## Reference
|
|
|
|
Full TUI documentation: [`docs/dev/pi-ui-tui/`](./dev/pi-ui-tui/README.md)
|