Make /share cancellable with Escape, add CHANGELOG entries

- Use BorderedLoader for /share command so Escape cancels gist creation
- Add CHANGELOG entries for /share command and HTML export improvements
- Add todo.md with remaining export-html issues
This commit is contained in:
Mario Zechner 2026-01-01 21:27:33 +01:00
parent 93aaf8160e
commit 738892eb7a
3 changed files with 48 additions and 24 deletions

View file

@ -2,10 +2,6 @@
## [Unreleased]
### Fixed
- Crash when displaying bash output containing Unicode format characters like U+0600-U+0604 ([#372](https://github.com/badlogic/pi-mono/pull/372) by [@HACKE-RC](https://github.com/HACKE-RC))
This release introduces session trees for in-place branching, major API changes to hooks and custom tools, and structured compaction with file tracking.
### Session Tree
@ -193,6 +189,9 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
### Added
- `/share` command to upload session as a secret GitHub gist and get a shareable URL via shittycodingagent.ai ([#380](https://github.com/badlogic/pi-mono/issues/380))
- HTML export now includes a tree visualization sidebar for navigating session branches ([#375](https://github.com/badlogic/pi-mono/issues/375))
- HTML export supports keyboard shortcuts: Ctrl+T to toggle thinking blocks, Ctrl+O to toggle tool outputs
- **Snake game example hook**: Demonstrates `ui.custom()`, `registerCommand()`, and session persistence. See [examples/hooks/snake.ts](examples/hooks/snake.ts).
- **`thinkingText` theme token**: Configurable color for thinking block text. ([#366](https://github.com/badlogic/pi-mono/pull/366) by [@paulbettner](https://github.com/paulbettner))
@ -200,9 +199,12 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
- **Entry IDs**: Session entries now use short 8-character hex IDs instead of full UUIDs
- **API key priority**: `ANTHROPIC_OAUTH_TOKEN` now takes precedence over `ANTHROPIC_API_KEY`
- HTML export template split into separate files (template.html, template.css, template.js) for easier maintenance
### Fixed
- HTML export now properly sanitizes user messages containing HTML tags like `<style>` that could break DOM rendering
- Crash when displaying bash output containing Unicode format characters like U+0600-U+0604 ([#372](https://github.com/badlogic/pi-mono/pull/372) by [@HACKE-RC](https://github.com/HACKE-RC))
- **Footer shows full session stats**: Token usage and cost now include all messages, not just those after compaction. ([#322](https://github.com/badlogic/pi-mono/issues/322))
- **Status messages spam chat log**: Rapidly changing settings (e.g., thinking level via Shift+Tab) would add multiple status lines. Sequential status updates now coalesce into a single line. ([#365](https://github.com/badlogic/pi-mono/pull/365) by [@paulbettner](https://github.com/paulbettner))
- **Toggling thinking blocks during streaming shows nothing**: Pressing Ctrl+T while streaming would hide the current message until streaming completed.

View file

@ -38,6 +38,7 @@ import { copyToClipboard } from "../../utils/clipboard.js";
import { ArminComponent } from "./components/armin.js";
import { AssistantMessageComponent } from "./components/assistant-message.js";
import { BashExecutionComponent } from "./components/bash-execution.js";
import { BorderedLoader } from "./components/bordered-loader.js";
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
import { CustomEditor } from "./components/custom-editor.js";
@ -1942,31 +1943,52 @@ export class InteractiveMode {
return;
}
// Show loader while creating gist
const loader = new Loader(
this.ui,
(spinner) => theme.fg("accent", spinner),
(text) => theme.fg("muted", text),
"Creating gist...",
);
this.statusContainer.addChild(loader);
// Show cancellable loader, replacing the editor
const loader = new BorderedLoader(this.ui, theme, "Creating gist...");
this.editorContainer.clear();
this.editorContainer.addChild(loader);
this.ui.setFocus(loader);
this.ui.requestRender();
const restoreEditor = () => {
loader.dispose();
this.editorContainer.clear();
this.editorContainer.addChild(this.editor);
this.ui.setFocus(this.editor);
try {
fs.unlinkSync(tmpFile);
} catch {
// Ignore cleanup errors
}
};
// Create a secret gist asynchronously
let proc: ReturnType<typeof spawn> | null = null;
loader.onAbort = () => {
proc?.kill();
restoreEditor();
this.showStatus("Share cancelled");
};
try {
const result = await new Promise<{ stdout: string; stderr: string; code: number | null }>((resolve) => {
const proc = spawn("gh", ["gist", "create", "--public=false", tmpFile]);
proc = spawn("gh", ["gist", "create", "--public=false", tmpFile]);
let stdout = "";
let stderr = "";
proc.stdout.on("data", (data) => {
proc.stdout?.on("data", (data) => {
stdout += data.toString();
});
proc.stderr.on("data", (data) => {
proc.stderr?.on("data", (data) => {
stderr += data.toString();
});
proc.on("close", (code) => resolve({ stdout, stderr, code }));
});
if (loader.signal.aborted) return;
restoreEditor();
if (result.code !== 0) {
const errorMsg = result.stderr?.trim() || "Unknown error";
this.showError(`Failed to create gist: ${errorMsg}`);
@ -1986,15 +2008,9 @@ export class InteractiveMode {
const previewUrl = `https://shittycodingagent.ai/session?${gistId}`;
this.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
} catch (error: unknown) {
this.showError(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`);
} finally {
// Stop loader and clean up
loader.stop();
this.statusContainer.clear();
try {
fs.unlinkSync(tmpFile);
} catch {
// Ignore cleanup errors
if (!loader.signal.aborted) {
restoreEditor();
this.showError(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
}