Reviewed PR description, comments, commits, and changed files.
UnderstandChecked linked issues and relevant data structures.
UnderstandAnalyzed code changes and theme loading logic.
UnderstandConfirmed fix correctness and missing changelog entry.
VerifyMerged PR via GitHub CLI and added a comment.
EditYou are given one or more GitHub PR URLs: https://github.com/badlogic/pi-mono/pull/946
For each PR URL, do the following in order:
packages/*/CHANGELOG.md files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
### Breaking Changes, ### Added, ### Fixed, etc.)Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))### Breaking Changes, not just ### FixedOutput format per PR: PR: Changelog:
If no issues are found, say so under Bad and Ugly.
>cd /Users/badlogic/workspaces/pi-mono && gh pr view 946 --json title,body,comments,commits,files,author,labels,state,headRefName,baseRefName{"author":{"id":"MDQ6VXNlcjczOTY=","is_bot":false,"login":"mitsuhiko","name":"Armin Ronacher"},"baseRefName":"main","body":"Without this, `/export` breaks if a theme comes from an npm package.","comments":[],"commits":[{"authoredDate":"2026-01-25T16:07:41Z","authors":[{"email":"[REDACTED]","id":"MDQ6VXNlcjczOTY=","login":"mitsuhiko","name":"Armin Ronacher"}],"committedDate":"2026-01-25T16:07:41Z","messageBody":"","messageHeadline":"Fixes distributed themes breaking /export","oid":"5dfef24f07aa2acafe9c504f5b8551cf4b927af9"}],"files":[{"path":"packages/coding-agent/src/modes/interactive/theme/theme.ts","additions":8,"deletions":0}],"headRefName":"fix-distributed-themes-for-share","labels":[],"state":"OPEN","title":"Fixes distributed themes breaking /export"}>cd /Users/badlogic/workspaces/pi-mono && gh pr diff 946diff --git a/packages/coding-agent/src/modes/interactive/theme/theme.ts b/packages/coding-agent/src/modes/interactive/theme/theme.ts
index 583245bbc..b00e7566b 100644
--- a/packages/coding-agent/src/modes/interactive/theme/theme.ts
+++ b/packages/coding-agent/src/modes/interactive/theme/theme.ts
@@ -552,6 +552,14 @@ function loadThemeJson(name: string): ThemeJson {
if (name in builtinThemes) {
return builtinThemes[name];
}
+ const registeredTheme = registeredThemes.get(name);
+ if (registeredTheme?.sourcePath) {
+ const content = fs.readFileSync(registeredTheme.sourcePath, "utf-8");
+ return parseThemeJsonContent(registeredTheme.sourcePath, content);
+ }
+ if (registeredTheme) {
+ throw new Error(`Theme "${name}" does not have a source path for export`);
+ }
const customThemesDir = getCustomThemesDir();
const themePath = path.join(customThemesDir, `${name}.json`);
if (!fs.existsSync(themePath)) {Now let me read the relevant code file in full to understand the context:
Now let me check for related issues linked in the PR, and examine the registeredThemes data structure and where sourcePath is used:
>cd /Users/badlogic/workspaces/pi-mono && rg -n "registeredThemes" packages/coding-agent/src --type ts -A 2 -B 2packages/coding-agent/src/modes/interactive/theme/theme.ts-463- }
packages/coding-agent/src/modes/interactive/theme/theme.ts-464- }
packages/coding-agent/src/modes/interactive/theme/theme.ts:465: for (const name of registeredThemes.keys()) {
packages/coding-agent/src/modes/interactive/theme/theme.ts-466- themes.add(name);
packages/coding-agent/src/modes/interactive/theme/theme.ts-467- }
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-496- }
packages/coding-agent/src/modes/interactive/theme/theme.ts-497-
packages/coding-agent/src/modes/interactive/theme/theme.ts:498: for (const [name, theme] of registeredThemes.entries()) {
packages/coding-agent/src/modes/interactive/theme/theme.ts-499- if (!result.some((t) => t.name === name)) {
packages/coding-agent/src/modes/interactive/theme/theme.ts-500- result.push({ name, path: theme.sourcePath });
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-595-
packages/coding-agent/src/modes/interactive/theme/theme.ts-596-function loadTheme(name: string, mode?: ColorMode): Theme {
packages/coding-agent/src/modes/interactive/theme/theme.ts:597: const registeredTheme = registeredThemes.get(name);
packages/coding-agent/src/modes/interactive/theme/theme.ts-598- if (registeredTheme) {
packages/coding-agent/src/modes/interactive/theme/theme.ts-599- return registeredTheme;
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-654-let themeWatcher: fs.FSWatcher | undefined;
packages/coding-agent/src/modes/interactive/theme/theme.ts-655-let onThemeChangeCallback: (() => void) | undefined;
packages/coding-agent/src/modes/interactive/theme/theme.ts:656:const registeredThemes = new Map<string, Theme>();
packages/coding-agent/src/modes/interactive/theme/theme.ts-657-
packages/coding-agent/src/modes/interactive/theme/theme.ts-658-export function setRegisteredThemes(themes: Theme[]): void {
packages/coding-agent/src/modes/interactive/theme/theme.ts:659: registeredThemes.clear();
packages/coding-agent/src/modes/interactive/theme/theme.ts-660- for (const theme of themes) {
packages/coding-agent/src/modes/interactive/theme/theme.ts-661- if (theme.name) {
packages/coding-agent/src/modes/interactive/theme/theme.ts:662: registeredThemes.set(theme.name, theme);
packages/coding-agent/src/modes/interactive/theme/theme.ts-663- }
packages/coding-agent/src/modes/interactive/theme/theme.ts-664- }>cd /Users/badlogic/workspaces/pi-mono && rg -n "sourcePath" packages/coding-agent/src --type ts -A 2 -B 2packages/coding-agent/src/modes/interactive/interactive-mode.ts-953- // Show loaded themes (excluding built-in)
packages/coding-agent/src/modes/interactive/interactive-mode.ts-954- const loadedThemes = this.session.resourceLoader.getThemes().themes;
packages/coding-agent/src/modes/interactive/interactive-mode.ts:955: const customThemes = loadedThemes.filter((t) => t.sourcePath);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-956- if (customThemes.length > 0) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts:957: const themePaths = customThemes.map((t) => t.sourcePath!);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-958- const groups = this.buildScopeGroups(themePaths, metadata);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-959- const themeList = this.formatScopeGroups(groups, {
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-336-export class Theme {
packages/coding-agent/src/modes/interactive/theme/theme.ts-337- readonly name?: string;
packages/coding-agent/src/modes/interactive/theme/theme.ts:338: readonly sourcePath?: string;
packages/coding-agent/src/modes/interactive/theme/theme.ts-339- private fgColors: Map<ThemeColor, string>;
packages/coding-agent/src/modes/interactive/theme/theme.ts-340- private bgColors: Map<ThemeBg, string>;
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-345- bgColors: Record<ThemeBg, string | number>,
packages/coding-agent/src/modes/interactive/theme/theme.ts-346- mode: ColorMode,
packages/coding-agent/src/modes/interactive/theme/theme.ts:347: options: { name?: string; sourcePath?: string } = {},
packages/coding-agent/src/modes/interactive/theme/theme.ts-348- ) {
packages/coding-agent/src/modes/interactive/theme/theme.ts-349- this.name = options.name;
packages/coding-agent/src/modes/interactive/theme/theme.ts:350: this.sourcePath = options.sourcePath;
packages/coding-agent/src/modes/interactive/theme/theme.ts-351- this.mode = mode;
packages/coding-agent/src/modes/interactive/theme/theme.ts-352- this.fgColors = new Map();
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-498- for (const [name, theme] of registeredThemes.entries()) {
packages/coding-agent/src/modes/interactive/theme/theme.ts-499- if (!result.some((t) => t.name === name)) {
packages/coding-agent/src/modes/interactive/theme/theme.ts:500: result.push({ name, path: theme.sourcePath });
packages/coding-agent/src/modes/interactive/theme/theme.ts-501- }
packages/coding-agent/src/modes/interactive/theme/theme.ts-502- }
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-562-}
packages/coding-agent/src/modes/interactive/theme/theme.ts-563-
packages/coding-agent/src/modes/interactive/theme/theme.ts:564:function createTheme(themeJson: ThemeJson, mode?: ColorMode, sourcePath?: string): Theme {
packages/coding-agent/src/modes/interactive/theme/theme.ts-565- const colorMode = mode ?? detectColorMode();
packages/coding-agent/src/modes/interactive/theme/theme.ts-566- const resolvedColors = resolveThemeColors(themeJson.colors, themeJson.vars);
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-584- return new Theme(fgColors, bgColors, colorMode, {
packages/coding-agent/src/modes/interactive/theme/theme.ts-585- name: themeJson.name,
packages/coding-agent/src/modes/interactive/theme/theme.ts:586: sourcePath,
packages/coding-agent/src/modes/interactive/theme/theme.ts-587- });
packages/coding-agent/src/modes/interactive/theme/theme.ts-588-}
--
packages/coding-agent/src/core/session-manager.ts-1235- * Fork a session from another project directory into the current project.
packages/coding-agent/src/core/session-manager.ts-1236- * Creates a new session in the target cwd with the full history from the source session.
packages/coding-agent/src/core/session-manager.ts:1237: * @param sourcePath Path to the source session file
packages/coding-agent/src/core/session-manager.ts-1238- * @param targetCwd Target working directory (where the new session will be stored)
packages/coding-agent/src/core/session-manager.ts-1239- * @param sessionDir Optional session directory. If omitted, uses default for targetCwd.
packages/coding-agent/src/core/session-manager.ts-1240- */
packages/coding-agent/src/core/session-manager.ts:1241: static forkFrom(sourcePath: string, targetCwd: string, sessionDir?: string): SessionManager {
packages/coding-agent/src/core/session-manager.ts:1242: const sourceEntries = loadEntriesFromFile(sourcePath);
packages/coding-agent/src/core/session-manager.ts-1243- if (sourceEntries.length === 0) {
packages/coding-agent/src/core/session-manager.ts:1244: throw new Error(`Cannot fork: source session file is empty or invalid: ${sourcePath}`);
packages/coding-agent/src/core/session-manager.ts-1245- }
packages/coding-agent/src/core/session-manager.ts-1246-
packages/coding-agent/src/core/session-manager.ts-1247- const sourceHeader = sourceEntries.find((e) => e.type === "session") as SessionHeader | undefined;
packages/coding-agent/src/core/session-manager.ts-1248- if (!sourceHeader) {
packages/coding-agent/src/core/session-manager.ts:1249: throw new Error(`Cannot fork: source session has no header: ${sourcePath}`);
packages/coding-agent/src/core/session-manager.ts-1250- }
packages/coding-agent/src/core/session-manager.ts-1251-
--
packages/coding-agent/src/core/session-manager.ts-1268- timestamp,
packages/coding-agent/src/core/session-manager.ts-1269- cwd: targetCwd,
packages/coding-agent/src/core/session-manager.ts:1270: parentSession: sourcePath,
packages/coding-agent/src/core/session-manager.ts-1271- };
packages/coding-agent/src/core/session-manager.ts-1272- appendFileSync(newSessionFile, `${JSON.stringify(newHeader)}\n`);
--
packages/coding-agent/src/core/resource-loader.ts-394- this.themeDiagnostics = resolvedThemes.diagnostics;
packages/coding-agent/src/core/resource-loader.ts-395- for (const theme of this.themes) {
packages/coding-agent/src/core/resource-loader.ts:396: if (theme.sourcePath) {
packages/coding-agent/src/core/resource-loader.ts:397: this.addDefaultMetadataForPath(theme.sourcePath);
packages/coding-agent/src/core/resource-loader.ts-398- }
packages/coding-agent/src/core/resource-loader.ts-399- }
--
packages/coding-agent/src/core/resource-loader.ts-426-
packages/coding-agent/src/core/resource-loader.ts-427- for (const p of [...primary, ...additional]) {
packages/coding-agent/src/core/resource-loader.ts:428: const resolved = this.resolveResourcePath(p);
packages/coding-agent/src/core/resource-loader.ts-429- if (seen.has(resolved)) continue;
packages/coding-agent/src/core/resource-loader.ts-430- seen.add(resolved);
--
packages/coding-agent/src/core/resource-loader.ts-435- }
packages/coding-agent/src/core/resource-loader.ts-436-
packages/coding-agent/src/core/resource-loader.ts:437: private resolveResourcePath(p: string): string {
packages/coding-agent/src/core/resource-loader.ts-438- const trimmed = p.trim();
packages/coding-agent/src/core/resource-loader.ts-439- let expanded = trimmed;
--
packages/coding-agent/src/core/resource-loader.ts-579- type: "collision",
packages/coding-agent/src/core/resource-loader.ts-580- message: `name "${name}" collision`,
packages/coding-agent/src/core/resource-loader.ts:581: path: t.sourcePath,
packages/coding-agent/src/core/resource-loader.ts-582- collision: {
packages/coding-agent/src/core/resource-loader.ts-583- resourceType: "theme",
packages/coding-agent/src/core/resource-loader.ts-584- name,
packages/coding-agent/src/core/resource-loader.ts:585: winnerPath: existing.sourcePath ?? "<builtin>",
packages/coding-agent/src/core/resource-loader.ts:586: loserPath: t.sourcePath ?? "<builtin>",
packages/coding-agent/src/core/resource-loader.ts-587- },
packages/coding-agent/src/core/resource-loader.ts-588- });Now let me check how registered themes are populated and whether they have sourcePath set, and look at the /export functionality:
>cd /Users/badlogic/workspaces/pi-mono && rg -n "loadThemeFromPath" packages/coding-agent/src --type ts -A 2 -B 2packages/coding-agent/src/modes/interactive/theme/theme.ts-588-}
packages/coding-agent/src/modes/interactive/theme/theme.ts-589-
packages/coding-agent/src/modes/interactive/theme/theme.ts:590:export function loadThemeFromPath(themePath: string, mode?: ColorMode): Theme {
packages/coding-agent/src/modes/interactive/theme/theme.ts-591- const content = fs.readFileSync(themePath, "utf-8");
packages/coding-agent/src/modes/interactive/theme/theme.ts-592- const themeJson = parseThemeJsonContent(themePath, content);
--
packages/coding-agent/src/core/resource-loader.ts-4-import chalk from "chalk";
packages/coding-agent/src/core/resource-loader.ts-5-import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
packages/coding-agent/src/core/resource-loader.ts:6:import { loadThemeFromPath, type Theme } from "../modes/interactive/theme/theme.js";
packages/coding-agent/src/core/resource-loader.ts-7-import type { ResourceDiagnostic } from "./diagnostics.js";
packages/coding-agent/src/core/resource-loader.ts-8-
--
packages/coding-agent/src/core/resource-loader.ts-514- private loadThemeFromFile(filePath: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {
packages/coding-agent/src/core/resource-loader.ts-515- try {
packages/coding-agent/src/core/resource-loader.ts:516: themes.push(loadThemeFromPath(filePath));
packages/coding-agent/src/core/resource-loader.ts-517- } catch (error) {
packages/coding-agent/src/core/resource-loader.ts-518- const message = error instanceof Error ? error.message : "failed to load theme";>cd /Users/badlogic/workspaces/pi-mono && rg -n "/export" packages/coding-agent/src --type ts -A 2 -B 2packages/coding-agent/src/config.ts-67- * Get path to HTML export template directory (shipped with package)
packages/coding-agent/src/config.ts-68- * - For Bun binary: export-html/ next to executable
packages/coding-agent/src/config.ts:69: * - For Node.js (dist/): dist/core/export-html/
packages/coding-agent/src/config.ts:70: * - For tsx (src/): src/core/export-html/
packages/coding-agent/src/config.ts-71- */
packages/coding-agent/src/config.ts-72-export function getExportTemplateDir(): string {
--
packages/coding-agent/src/main.ts-16-import { getAgentDir, getModelsPath, VERSION } from "./config.js";
packages/coding-agent/src/main.ts-17-import { AuthStorage } from "./core/auth-storage.js";
packages/coding-agent/src/main.ts:18:import { exportFromFile } from "./core/export-html/index.js";
packages/coding-agent/src/main.ts-19-import type { LoadExtensionsResult } from "./core/extensions/index.js";
packages/coding-agent/src/main.ts-20-import { KeybindingsManager } from "./core/keybindings.js";
--
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1815- return;
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1816- }
packages/coding-agent/src/modes/interactive/interactive-mode.ts:1817: if (text.startsWith("/export")) {
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1818- await this.handleExportCommand(text);
packages/coding-agent/src/modes/interactive/interactive-mode.ts-1819- this.editor.setText("");
--
packages/coding-agent/src/core/agent-session.ts-40- shouldCompact,
packages/coding-agent/src/core/agent-session.ts-41-} from "./compaction/index.js";
packages/coding-agent/src/core/agent-session.ts:42:import { exportSessionToHtml, type ToolHtmlRenderer } from "./export-html/index.js";
packages/coding-agent/src/core/agent-session.ts:43:import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
packages/coding-agent/src/core/agent-session.ts-44-import {
packages/coding-agent/src/core/agent-session.ts-45- type ContextUsage,
--
packages/coding-agent/src/core/export-html/index.ts-220-/**
packages/coding-agent/src/core/export-html/index.ts-221- * Export session to HTML using SessionManager and AgentState.
packages/coding-agent/src/core/export-html/index.ts:222: * Used by TUI's /export command.
packages/coding-agent/src/core/export-html/index.ts-223- */
packages/coding-agent/src/core/export-html/index.ts-224-export async function exportSessionToHtml(Let me look at how themes from npm packages are loaded (the resource-loader) and how they end up with sourcePath:
Now let me look at the export HTML functionality and see where loadThemeJson is called:
>cd /Users/badlogic/workspaces/pi-mono && rg -n "loadThemeJson" packages/coding-agent/src --type ts -A 2 -B 2packages/coding-agent/src/modes/interactive/theme/theme.ts-548-}
packages/coding-agent/src/modes/interactive/theme/theme.ts-549-
packages/coding-agent/src/modes/interactive/theme/theme.ts:550:function loadThemeJson(name: string): ThemeJson {
packages/coding-agent/src/modes/interactive/theme/theme.ts-551- const builtinThemes = getBuiltinThemes();
packages/coding-agent/src/modes/interactive/theme/theme.ts-552- if (name in builtinThemes) {
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-599- return registeredTheme;
packages/coding-agent/src/modes/interactive/theme/theme.ts-600- }
packages/coding-agent/src/modes/interactive/theme/theme.ts:601: const themeJson = loadThemeJson(name);
packages/coding-agent/src/modes/interactive/theme/theme.ts-602- return createTheme(themeJson, mode);
packages/coding-agent/src/modes/interactive/theme/theme.ts-603-}
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-839- const name = themeName ?? currentThemeName ?? getDefaultTheme();
packages/coding-agent/src/modes/interactive/theme/theme.ts-840- const isLight = name === "light";
packages/coding-agent/src/modes/interactive/theme/theme.ts:841: const themeJson = loadThemeJson(name);
packages/coding-agent/src/modes/interactive/theme/theme.ts-842- const resolved = resolveThemeColors(themeJson.colors, themeJson.vars);
packages/coding-agent/src/modes/interactive/theme/theme.ts-843-
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-878- const name = themeName ?? currentThemeName ?? getDefaultTheme();
packages/coding-agent/src/modes/interactive/theme/theme.ts-879- try {
packages/coding-agent/src/modes/interactive/theme/theme.ts:880: const themeJson = loadThemeJson(name);
packages/coding-agent/src/modes/interactive/theme/theme.ts-881- const exportSection = themeJson.export;
packages/coding-agent/src/modes/interactive/theme/theme.ts-882- if (!exportSection) return {};Now I understand the issue better. Let me check where getResolvedThemeColors and getThemeExportColors are called, which both use loadThemeJson:
>cd /Users/badlogic/workspaces/pi-mono && rg -n "getResolvedThemeColors|getThemeExportColors" packages/coding-agent/src --type ts -A 2 -B 2packages/coding-agent/src/modes/interactive/theme/theme.ts-836- * Used by HTML export to generate CSS custom properties.
packages/coding-agent/src/modes/interactive/theme/theme.ts-837- */
packages/coding-agent/src/modes/interactive/theme/theme.ts:838:export function getResolvedThemeColors(themeName?: string): Record<string, string> {
packages/coding-agent/src/modes/interactive/theme/theme.ts-839- const name = themeName ?? currentThemeName ?? getDefaultTheme();
packages/coding-agent/src/modes/interactive/theme/theme.ts-840- const isLight = name === "light";
--
packages/coding-agent/src/modes/interactive/theme/theme.ts-871- * Returns undefined for each color that isn't explicitly set.
packages/coding-agent/src/modes/interactive/theme/theme.ts-872- */
packages/coding-agent/src/modes/interactive/theme/theme.ts:873:export function getThemeExportColors(themeName?: string): {
packages/coding-agent/src/modes/interactive/theme/theme.ts-874- pageBg?: string;
packages/coding-agent/src/modes/interactive/theme/theme.ts-875- cardBg?: string;
--
packages/coding-agent/src/core/export-html/index.ts-3-import { basename, join } from "path";
packages/coding-agent/src/core/export-html/index.ts-4-import { APP_NAME, getExportTemplateDir } from "../../config.js";
packages/coding-agent/src/core/export-html/index.ts:5:import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme.js";
packages/coding-agent/src/core/export-html/index.ts-6-import type { SessionEntry } from "../session-manager.js";
packages/coding-agent/src/core/export-html/index.ts-7-import { SessionManager } from "../session-manager.js";
--
packages/coding-agent/src/core/export-html/index.ts-106- */
packages/coding-agent/src/core/export-html/index.ts-107-function generateThemeVars(themeName?: string): string {
packages/coding-agent/src/core/export-html/index.ts:108: const colors = getResolvedThemeColors(themeName);
packages/coding-agent/src/core/export-html/index.ts-109- const lines: string[] = [];
packages/coding-agent/src/core/export-html/index.ts-110- for (const [key, value] of Object.entries(colors)) {
--
packages/coding-agent/src/core/export-html/index.ts-113-
packages/coding-agent/src/core/export-html/index.ts-114- // Use explicit theme export colors if available, otherwise derive from userMessageBg
packages/coding-agent/src/core/export-html/index.ts:115: const themeExport = getThemeExportColors(themeName);
packages/coding-agent/src/core/export-html/index.ts-116- const userMessageBg = colors.userMessageBg || "#343541";
packages/coding-agent/src/core/export-html/index.ts-117- const derivedColors = deriveExportColors(userMessageBg);
--
packages/coding-agent/src/core/export-html/index.ts-146-
packages/coding-agent/src/core/export-html/index.ts-147- const themeVars = generateThemeVars(themeName);
packages/coding-agent/src/core/export-html/index.ts:148: const colors = getResolvedThemeColors(themeName);
packages/coding-agent/src/core/export-html/index.ts-149- const exportColors = deriveExportColors(colors.userMessageBg || "#343541");
packages/coding-agent/src/core/export-html/index.ts-150- const bodyBg = exportColors.pageBg;Now I have a full picture. Let me check the coding-agent docs to see if modifications are required:
>cd /Users/badlogic/workspaces/pi-mono && ls -la packages/coding-agent/docs/total 440
drwxr-xr-x@ 12 badlogic staff 384 Jan 25 03:33 .
drwxr-xr-x@ 18 badlogic staff 576 Jan 25 19:08 ..
-rw-r--r--@ 1 badlogic staff 14757 Jan 3 00:47 compaction.md
-rw-r--r--@ 1 badlogic staff 16373 Jan 25 03:33 custom-provider.md
-rw-r--r--@ 1 badlogic staff 60359 Jan 25 03:33 extensions.md
-rw-r--r--@ 1 badlogic staff 23979 Jan 16 23:14 rpc.md
-rw-r--r--@ 1 badlogic staff 27799 Jan 25 03:33 sdk.md
-rw-r--r--@ 1 badlogic staff 10015 Jan 19 00:18 session.md
-rw-r--r--@ 1 badlogic staff 8101 Jan 25 03:33 skills.md
-rw-r--r--@ 1 badlogic staff 16734 Jan 8 19:37 theme.md
-rw-r--r--@ 1 badlogic staff 6645 Jan 16 21:49 tree.md
-rw-r--r--@ 1 badlogic staff 26947 Jan 19 15:55 tui.md>cd /Users/badlogic/workspaces/pi-mono && rg -l "theme" packages/coding-agent/docs/ packages/coding-agent/README.mdpackages/coding-agent/README.md
packages/coding-agent/docs/theme.md
packages/coding-agent/docs/sdk.md
packages/coding-agent/docs/extensions.md
packages/coding-agent/docs/tui.mdLet me verify the fix is complete by examining what loadThemeFromPath returns vs what this change does:
Now let me view the diff again to fully understand what the PR change does:
The PR adds code to loadThemeJson() (lines 552-560) that:
registeredThemessourcePath, reads the JSON from that pathsourcePath, throws an errorThe issue is that when themes come from npm packages, they are loaded via loadThemeFromPath() which sets sourcePath, but the loadTheme() function returns the Theme instance directly from registeredThemes without re-reading the JSON. The /export feature calls getResolvedThemeColors() and getThemeExportColors() which both call loadThemeJson(), but loadThemeJson() didn't previously know about registered themes.
Let me verify this understanding by checking if there are any tests:
>cd /Users/badlogic/workspaces/pi-mono && rg -l "loadThemeJson|registeredThemes|loadThemeFromPath" packages/coding-agent/test --type ts 2>/dev/null || echo "No test files found"No test files found>cd /Users/badlogic/workspaces/pi-mono && ls packages/coding-agent/test/ 2>/dev/null | head -20 || echo "No test directory"agent-session-branching.test.ts
agent-session-compaction.test.ts
agent-session-concurrent.test.ts
agent-session-tree-navigation.test.ts
args.test.ts
block-images.test.ts
clipboard-image.test.ts
compaction-extensions-example.test.ts
compaction-extensions.test.ts
compaction-thinking-model.test.ts
compaction.test.ts
extensions-discovery.test.ts
extensions-input-event.test.ts
extensions-runner.test.ts
fixtures
frontmatter.test.ts
image-processing.test.ts
interactive-mode-status.test.ts
model-registry.test.ts
model-resolver.test.tsNo theme-specific tests exist for this functionality. Now let me provide my comprehensive review:
PR: https://github.com/badlogic/pi-mono/pull/946
Changelog:
packages/coding-agent/CHANGELOG.md for this fix. A changelog entry is required before merge. I will add it if you decide to merge.Good:
/export now works with themes from npm packages by checking registeredThemes and reading the theme JSON from sourcePath when availablesourcePath is clear: Theme "${name}" does not have a source path for exportloadThemeJson() without changing other theme loading pathsBad:
pkg:coding-agent label on the PRUgly:
Questions or Assumptions:
sourcePath would be unusual (would only happen if someone called setRegisteredThemes() with Theme instances created without a sourcePath). The error handling is appropriate.loadThemeFromPath() always sets sourcePath, so themes loaded from npm packages via ResourceLoader will always have the path available.Change summary:
/export, the loadThemeJson() function is called to get the raw theme JSON for CSS generationloadThemeJson() only checked builtin themes (dark/light) and the custom themes directory (~/.pi/agent/themes/)setRegisteredThemes() as Theme instances (not JSON), so they were not found, causing /export to fail with "Theme not found"registeredThemes and reads the JSON from the theme's sourcePath (the original file location in node_modules)Tests:
/exportDocumentation:
packages/coding-agent/docs/theme.md doesn't need updates as it already covers npm package themes.merge via gh cli and pull from origin, add a comment "Thanks"
>cd /Users/badlogic/workspaces/pi-mono && gh pr merge 946 --squash --body "Thanks"(no output)>cd /Users/badlogic/workspaces/pi-mono && git pull origin mainerror: cannot pull with rebase: You have unstaged changes.
error: Please commit or stash them.
Command exited with code 128>cd /Users/badlogic/workspaces/pi-mono && git statusOn branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: package-lock.json
modified: packages/ai/src/models.generated.ts
Untracked files:
(use "git add <file>..." to include in what will be committed)
.pi/settings.json
no changes added to commit (use "git add" and/or "git commit -a")>cd /Users/badlogic/workspaces/pi-mono && git stash && git pull origin main && git stash popSaved working directory and index state WIP on main: b1211261 docs(coding-agent): add changelog entry for extension shortcut fix (#947)
From https://github.com/badlogic/pi-mono
* branch main -> FETCH_HEAD
b1211261..5555864c main -> origin/main
Updating b1211261..5555864c
Fast-forward
packages/coding-agent/CHANGELOG.md | 9 ++++-----
packages/coding-agent/src/core/export-html/template.css | 9 +++++++++
packages/coding-agent/src/core/export-html/template.js | 10 ++++++----
packages/coding-agent/src/modes/interactive/theme/theme.ts | 8 ++++++++
4 files changed, 27 insertions(+), 9 deletions(-)
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: package-lock.json
modified: packages/ai/src/models.generated.ts
Untracked files:
(use "git add <file>..." to include in what will be committed)
.pi/settings.json
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (743ed3b1a11bdf67d47035881a2edb4aac8093ab)>cd /Users/badlogic/workspaces/pi-mono && gh pr comment 946 --body "Thanks"https://github.com/badlogic/pi-mono/pull/946#issuecomment-3797054189Done. PR #946 merged via squash, pulled from origin, and left a "Thanks" comment.