@anthodev/pi-tools-style 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ Built-in tools, MCP integrations, AFT, and third-party extensions keep control o
25
25
 
26
26
  ## Installation
27
27
 
28
- The current release targets Pi `0.84.x` and requires Node.js 22 or newer.2
28
+ This checkout targets Pi `1.0.x`, verified with `1.0.0`, and requires Node.js `>=22.19.0`.
29
29
 
30
30
  To install it, use the following command:
31
31
 
@@ -135,7 +135,7 @@ Borders resolve their color from the active Pi theme on every render. Switching
135
135
 
136
136
  ## How it works
137
137
 
138
- Pi `0.84.x` does not expose public render middleware around `ToolExecutionComponent` and `BashExecutionComponent`. `pi-tools-style` therefore installs a narrow wrapper around their existing `render()` methods.
138
+ Pi `1.0.x` does not expose public, composable render middleware covering both tools and direct shell commands. `pi-tools-style` therefore installs a narrow wrapper around the existing `render()` methods of `ToolExecutionComponent` and `BashExecutionComponent`.
139
139
 
140
140
  The wrapper asks the previous renderer to draw at the frame's inner width, removes only paired outer horizontal rules, and then adds ANSI-aware chrome. It does not call `pi.registerTool()`, replace tool definitions, or intercept execution.
141
141
 
@@ -143,12 +143,13 @@ Configuration and animation state live behind `Symbol.for()` keys. Reloading upd
143
143
 
144
144
  ## Compatibility and limits
145
145
 
146
- - Pi `0.84.x` is the supported target. Private TUI internals may change in later releases.
146
+ - Pi `1.0.x` is the supported target, verified with `1.0.0` in both `fullscreen` and `regular` TUI modes. Pi `0.84.x` is no longer a supported target. Private TUI internals may change in later releases.
147
147
  - The render wrapper is necessarily unsupported until Pi exposes public, composable render middleware.
148
148
  - Kitty, iTerm2, and Sixel image output remains unboxed by design.
149
149
  - Slash commands remain outside the current scope because their transcript output has no uniform component.
150
150
  - Another extension can replace this wrapper if it overwrites component rendering without composing with the previous renderer.
151
151
  - Unsupported renderer shapes fail open rather than affecting tool execution.
152
+ - Frame characters remain selectable text and are included in copied output. Excluding decorative cells would require Pi/TUI integration for fullscreen selection; no portable exclusion path has been identified for native terminal selection in regular mode.
152
153
 
153
154
  ## Development
154
155
 
@@ -160,10 +161,36 @@ npm run check
160
161
  npm pack --dry-run --json
161
162
  ```
162
163
 
163
- `npm run check` runs TypeScript validation and the Vitest suite. Tests cover frame width, ANSI output, terminal images, native and AFT rule removal, category colors, icon modes, persisted settings, spinner lifecycle, real `ToolExecutionComponent` integration, render composition, reload idempotence, fail-open behavior, and the no-`registerTool()` contract.
164
+ `npm run check` runs TypeScript validation and the Vitest suite. Tests cover frame width, ANSI output, terminal images, native and AFT rule removal, category colors, icon modes, persisted settings, spinner lifecycle, real `ToolExecutionComponent` and `BashExecutionComponent` integration, third-party renderer composition, decorator toggling and reinstallation, reload idempotence, fail-open behavior, and the no-`registerTool()` contract.
165
+
166
+ Release-note tests cover exact version sections, Markdown fences and whitespace, duplicate or missing sections, tagged Git snapshots, annotated and lightweight tags, and CLI failure paths.
164
167
 
165
168
  `--legacy-peer-deps` avoids an npm 10.9.8 Arborist failure observed while resolving the development peer dependency graph.
166
169
 
170
+ ## Releases
171
+
172
+ Releases are triggered only by stable `vX.Y.Z` tag pushes. `.github/workflows/publish.yml` validates the tagged commit on Node.js `22.19.0` and `24.x`, packages one npm tarball, publishes it to npm, then creates the GitHub release with that tarball and `SHA256SUMS` as assets.
173
+
174
+ `CHANGELOG.md` is the source of release notes. CI extracts the exact version section from the tagged commit, not the working tree, and preserves its summary, lists, links, and Markdown. Invalid tags, mismatched revisions, or missing, empty, or duplicate sections fail before publication. CI neither generates notes from commit subjects nor edits the changelog.
175
+
176
+ To prepare a release:
177
+
178
+ 1. Update `package.json` and regenerate `package-lock.json`. Move the release entries from `Unreleased` into a dated `## [X.Y.Z] - YYYY-MM-DD` section, preserving verified commit or PR references and the full changelog comparison link.
179
+ 2. Run `PI_CODING_AGENT_DIR="$(mktemp -d)" npm run check` and `npm run pack:check`.
180
+ 3. Seal and push the release-preparation commit on `develop`. With jj, use the sealed commit's explicit SHA, never an empty working-copy `@`.
181
+ 4. Set `VERSION` to the prepared package version and `RELEASE_SHA` to that commit's full SHA, then validate the notes before pushing the tag:
182
+
183
+ ```bash
184
+ jj tag set "v${VERSION}" -r "$RELEASE_SHA"
185
+ notes="$(mktemp)"
186
+ node scripts/generate-release-notes.ts "v${VERSION}" "$RELEASE_SHA" "$notes"
187
+ jj git push --tag "v${VERSION}"
188
+ ```
189
+
190
+ The workflow requires the tag to match the package and lockfile versions and to belong to `develop`. npm publication uses OIDC Trusted Publishing on a GitHub-hosted runner with the protected `npm` environment. Keep the `publish.yml` filename and environment aligned with the trusted publisher configuration. Concurrent release tags are serialized.
191
+
192
+ Publication is not an idempotent update. An already-published npm version or existing GitHub release makes a rerun fail. npm publication precedes GitHub release creation, so a failure in the latter can leave the package already published. Do not move a published tag or delete a release to make a rerun succeed.
193
+
167
194
  ## License
168
195
 
169
196
  MIT
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@anthodev/pi-tools-style",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Presentation-only boxes for Pi tool calls and shell commands",
5
- "keywords": ["pi-package", "pi-extension", "tui", "tools"],
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-extension",
8
+ "tui",
9
+ "tools"
10
+ ],
6
11
  "type": "module",
7
12
  "license": "MIT",
8
13
  "repository": {
@@ -35,12 +40,12 @@
35
40
  "access": "public"
36
41
  },
37
42
  "peerDependencies": {
38
- "@earendil-works/pi-coding-agent": "0.84.x",
39
- "@earendil-works/pi-tui": "0.84.x"
43
+ "@earendil-works/pi-coding-agent": "1.0.x",
44
+ "@earendil-works/pi-tui": "1.0.x"
40
45
  },
41
46
  "devDependencies": {
42
- "@earendil-works/pi-coding-agent": "0.84.1",
43
- "@earendil-works/pi-tui": "0.84.1",
47
+ "@earendil-works/pi-coding-agent": "1.0.0",
48
+ "@earendil-works/pi-tui": "1.0.0",
44
49
  "@types/node": "24.12.0",
45
50
  "typescript": "5.9.3",
46
51
  "vitest": "4.1.9"
@@ -34,6 +34,23 @@ interface StoredConfig extends Omit<RenderDecoratorConfig, "enabled"> {
34
34
  enabled: boolean;
35
35
  }
36
36
 
37
+ interface ResolvedFrameOptions {
38
+ options: FrameOptions;
39
+ styleSignature: string | undefined;
40
+ title: string | undefined;
41
+ }
42
+
43
+ interface RenderCacheEntry {
44
+ config: StoredConfig;
45
+ output: string[];
46
+ rendered: readonly string[];
47
+ styleSignature: string | undefined;
48
+ title: string | undefined;
49
+ width: number;
50
+ }
51
+
52
+ const BORDER_STYLE_PROBE = "╭─╮│╰─╯";
53
+
37
54
  export function installRenderDecorator<T extends Renderable>(
38
55
  component: RenderableConstructor<T>,
39
56
  config: RenderDecoratorConfig<T>,
@@ -96,6 +113,8 @@ function createDecoratedRenderer(
96
113
  key: symbol,
97
114
  renderOriginal: RenderFunction,
98
115
  ): RenderFunction {
116
+ const cache = new WeakMap<Renderable, RenderCacheEntry>();
117
+
99
118
  return function decoratedRender(width: number): string[] {
100
119
  const current = readConfig(key);
101
120
  if (!current?.enabled) return renderOriginal.call(this, width);
@@ -109,22 +128,38 @@ function createDecoratedRenderer(
109
128
  }
110
129
 
111
130
  try {
112
- return decorateLines(this, rendered, width, current);
131
+ const resolved = resolveFrameOptions(this, current);
132
+ const cached = cache.get(this);
133
+ if (
134
+ cached?.config === current &&
135
+ cached.width === width &&
136
+ cached.title === resolved.title &&
137
+ cached.styleSignature === resolved.styleSignature &&
138
+ linesEqual(cached.rendered, rendered)
139
+ ) {
140
+ return cached.output;
141
+ }
142
+
143
+ const output = decorateLines(rendered, width, current, resolved.options);
144
+ cache.set(this, {
145
+ config: current,
146
+ output,
147
+ rendered: [...rendered],
148
+ styleSignature: resolved.styleSignature,
149
+ title: resolved.title,
150
+ width,
151
+ });
152
+ return output;
113
153
  } catch {
114
154
  return renderOriginal.call(this, width);
115
155
  }
116
156
  };
117
157
  }
118
158
 
119
- function decorateLines(
159
+ function resolveFrameOptions(
120
160
  instance: Renderable,
121
- rendered: readonly string[],
122
- width: number,
123
161
  config: StoredConfig,
124
- ): string[] {
125
- const lines = config.trimOuterRules
126
- ? trimOuterRules(rendered)
127
- : [...rendered];
162
+ ): ResolvedFrameOptions {
128
163
  const title = config.title?.(instance);
129
164
  const options: FrameOptions = {};
130
165
 
@@ -137,9 +172,34 @@ function decorateLines(
137
172
  }
138
173
  if (title) options.title = title;
139
174
 
175
+ return {
176
+ options,
177
+ // Theme providers can change border output without changing component content.
178
+ styleSignature: styleBorder?.(BORDER_STYLE_PROBE),
179
+ title,
180
+ };
181
+ }
182
+
183
+ function decorateLines(
184
+ rendered: readonly string[],
185
+ width: number,
186
+ config: StoredConfig,
187
+ options: FrameOptions,
188
+ ): string[] {
189
+ const lines = config.trimOuterRules
190
+ ? trimOuterRules(rendered)
191
+ : [...rendered];
192
+
140
193
  return frameLines(lines, width, options);
141
194
  }
142
195
 
196
+ function linesEqual(left: readonly string[], right: readonly string[]): boolean {
197
+ return (
198
+ left.length === right.length &&
199
+ left.every((line, index) => line === right[index])
200
+ );
201
+ }
202
+
143
203
  function storeConfig<T extends Renderable>(
144
204
  config: RenderDecoratorConfig<T>,
145
205
  ): void {