diff --git a/AGENTS.md b/AGENTS.md index e17b6a6..65cadd2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,6 +56,7 @@ Avoid fixed sleeps in both unit and e2e tests. Prefer event-driven helpers that - `extensions/processes-logs/` - `/ps:logs` command and log overlay - `extensions/processes-dock/` - `/ps:dock`, `/ps:pin` commands, dock widget, status widget, `COMMAND_PIN` handler - `extensions/shared/` - shared UI helpers used across all three extensions: `ui.ts` (`statusDot`, `processStatusTone`, `LineComponent`), `display-text.ts` (`sanitizeForDisplay`), `truncate.ts` (ANSI-safe `truncateToWidth`), `log-line.ts` (`renderLogLine`), `line-buffer.ts` +- `plugins/` - repo-local Biome GritQL lint plugins, registered in `biome.json` - `skills/` - shipped package skills consumed by Pi - `.agents/skills/` - local repo-only skills for development workflows @@ -69,6 +70,15 @@ Build TUI output with `Container` + `addChild`. Do not join strings and pass the - Use a class extending `Container` (or implementing `Component`) for reused UI pieces. - Use inline `Container` composition for one-off render trees. +### Untrusted text + +Process output, names, commands, and cwd are untrusted display text. Never interpolate them into a rendered string raw. + +- Log lines: `renderLogLine` from `extensions/shared/log-line.ts`. +- Bounded labels: `truncateForDisplay` from `extensions/shared/display-text.ts`. +- Everything else: `sanitizeForDisplay`. +- Truncate with `truncateToWidth` from `extensions/shared/truncate.ts`, never the one from `@earendil-works/pi-tui`. Pi's version injects `ESC[0m` and mis-parses non-SGR escape sequences; `plugins/no-pi-tui-truncate.grit` fails the lint if it is imported. + ## Builder pattern for UI helpers UI helper functions return a `Component`; they never mutate a passed-in container. diff --git a/biome.json b/biome.json index a0ae8f0..cca74ba 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,8 @@ "./node_modules/@aliou/biome-plugins/plugins/no-emojis.grit", "./node_modules/@aliou/biome-plugins/plugins/no-inner-types.grit", "./node_modules/@aliou/biome-plugins/plugins/no-buried-await.grit", - "./node_modules/@aliou/biome-plugins/plugins/no-empty-catch.grit" + "./node_modules/@aliou/biome-plugins/plugins/no-empty-catch.grit", + "./plugins/no-pi-tui-truncate.grit" ], "vcs": { "enabled": true, diff --git a/plugins/no-pi-tui-truncate.grit b/plugins/no-pi-tui-truncate.grit new file mode 100644 index 0000000..eb902e1 --- /dev/null +++ b/plugins/no-pi-tui-truncate.grit @@ -0,0 +1,18 @@ +engine biome(1.0) +language js(typescript) + +// Pi's truncateToWidth injects ESC[0m after the kept prefix and around the +// ellipsis, so a caller-applied color or background ends early and the +// ellipsis and padding render unstyled. Its CSI parser also only accepts the +// finals m, G, K, H, and J, so any other escape sequence swallows visible text +// and skews the measured width. extensions/shared/truncate.ts fixes both. +JsImport() as $import where { + $import <: contains JsModuleSource() as $source, + $source <: r"[\"']@earendil-works/pi-tui[\"']", + $import <: r"(?s).*truncateToWidth.*", + register_diagnostic( + span = $import, + message = "Import truncateToWidth from extensions/shared/truncate, not @earendil-works/pi-tui. Pi's version injects resets that break caller styling and mis-parses non-SGR escape sequences.", + severity = "error" + ) +}