Skip to content

Commit 2547bce

Browse files
Itay Inbarclaude
andcommitted
v1.9.1: move Plan Mode to alt+p so shift+tab stays pi's thinking cycle (#47)
v1.9.0 rebound pi's built-in app.thinking.cycle to alt+t so the plan-mode extension could claim shift+tab. That collided with pi's documented thinking shortcut and pi (>= 0.79) surfaced an [Extension issues] warning whenever the rebind hadn't taken yet. Plan Mode now registers on alt+p (unbound by pi, no shadowing), and the launcher performs a one-time cleanup of the v1.9.0 rewrite: if the user's keybindings.json still has app.thinking.cycle: "alt+t" exactly, that entry is removed; any binding the user set themselves is preserved. Verified end-to-end against a real little-coder run with a fresh PI_CODING_AGENT_DIR: extension loads with no [Extension issues] warning, and the upgrade scenario (seeded v1.9.0 leftover) is cleaned on first launch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 991bd42 commit 2547bce

5 files changed

Lines changed: 42 additions & 35 deletions

File tree

.pi/extensions/plan-mode/index.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import { PlanStatus } from "./status.ts";
1212

1313
// Plan Mode — a Claude-Code-style "research, ask, then plan" flow.
1414
//
15-
// shift+tab toggles plan mode (an indicator appears below the input). While it
16-
// is on, submitting a prompt does NOT run a normal coding turn; instead the
15+
// alt+p toggles plan mode (an indicator appears below the input). While it is
16+
// on, submitting a prompt does NOT run a normal coding turn; instead the
1717
// extension orchestrates:
1818
// 1. decompose the request into 1-4 exploration tasks (a reasoning sub-coder),
1919
// 2. dispatch those as read-only explorer sub-coders (isolated context; only
@@ -27,9 +27,8 @@ import { PlanStatus } from "./status.ts";
2727
// child little-coder (spawned via ../subagent/spawn.ts), and the final plan is
2828
// injected as a normal turn via pi.sendUserMessage so it lands in the chat.
2929
//
30-
// shift+tab is normally pi's thinking-level cycle; extension shortcuts take
31-
// precedence (custom-editor.js checks them first), so we shadow it and move the
32-
// thinking-level cycle to alt+t to keep it reachable.
30+
// alt+p is unbound by pi, so the extension can claim it cleanly without
31+
// shadowing any built-in (shift+tab stays pi's thinking-level cycle — issue #47).
3332

3433
const honey = (s: string) => `\x1b[38;2;225;90;31m${s}\x1b[39m`;
3534
const gray = (s: string) => `\x1b[90m${s}\x1b[39m`;
@@ -50,7 +49,7 @@ let pendingSynthesis: { digest: string; answers: string } | null = null;
5049
let synthesisActive = false;
5150

5251
function indicatorLines(): string[] {
53-
return [`${honey("◆")} ${honey("PLAN MODE")} ${gray("(shift+tab to exit)")}`];
52+
return [`${honey("◆")} ${honey("PLAN MODE")} ${gray("(alt+p to exit)")}`];
5453
}
5554

5655
function setIndicator(ctx: any, on: boolean): void {
@@ -269,8 +268,10 @@ async function orchestrate(pi: ExtensionAPI, ctx: any, prompt: string): Promise<
269268
}
270269

271270
export default function (pi: ExtensionAPI) {
272-
// shift+tab toggles plan mode (shadows pi's thinking-level cycle).
273-
pi.registerShortcut("shift+tab", {
271+
// alt+p toggles plan mode. pi leaves alt+p unbound, so this doesn't collide
272+
// with any built-in and shift+tab stays bound to pi's thinking-level cycle
273+
// (issue #47).
274+
pi.registerShortcut("alt+p", {
274275
description: "Toggle plan mode",
275276
handler: (ctx: any) => {
276277
if (orchestrating) return; // mid-plan: ignore toggles
@@ -280,10 +281,6 @@ export default function (pi: ExtensionAPI) {
280281
},
281282
});
282283

283-
// The thinking-level cycle keeps working on alt+t: the launcher rebinds pi's
284-
// built-in `app.thinking.cycle` from shift+tab to alt+t so shift+tab is free
285-
// for plan mode (see bin/little-coder.mjs §8b). No extension shortcut needed.
286-
287284
// Intercept a submitted prompt while plan mode is on and run the orchestration
288285
// instead of a normal coding turn.
289286
pi.on("input", async (event, ctx) => {
@@ -357,7 +354,7 @@ export default function (pi: ExtensionAPI) {
357354
});
358355
} else {
359356
(ctx as any).ui?.notify?.(
360-
"plan not implemented — refine your request, or shift+tab to leave plan mode",
357+
"plan not implemented — refine your request, or alt+p to leave plan mode",
361358
"info",
362359
);
363360
}

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to little-coder are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and little-coder's public interface (CLI, providers, tools, skills) follows semver starting at `v0.0.1` post-rename.
44

5+
## [v1.9.1] — 2026-06-08
6+
7+
### Fixed
8+
- **Plan Mode shortcut moved to `alt+p` so `shift+tab` stays pi's thinking-level cycle** ([#47](https://github.com/itayinbarr/little-coder/issues/47)). v1.9.0 claimed `shift+tab` for Plan Mode by rebinding pi's built-in `app.thinking.cycle` to `alt+t` in `~/.pi/agent/keybindings.json`. That collided with the muscle memory of every existing pi user — `shift+tab` is the documented thinking cycle — and pi (≥ 0.79) also surfaced an `[Extension issues]` warning whenever the rebind hadn't taken yet. Plan Mode now registers on **`alt+p`** instead (unbound by pi, so the extension claims it cleanly with no shadowing), and `shift+tab` returns to pi's default behavior. The launcher also performs a **one-time cleanup**: on first run after upgrade, if `~/.pi/agent/keybindings.json` still has the v1.9.0 rewrite (`app.thinking.cycle: "alt+t"` exactly), it is removed; any binding you set yourself is preserved untouched. README and the Plan-Mode indicator (`(alt+p to exit)`) updated to match.
9+
10+
### Notes for upgraders
11+
- No CLI-flag or public-API changes. **Plan Mode is now `alt+p`** (was `shift+tab` in v1.9.0). `shift+tab` is again pi's thinking-level cycle. If you customized `app.thinking.cycle` yourself in `~/.pi/agent/keybindings.json`, your binding is left alone.
12+
13+
---
14+
515
## [v1.9.0] — 2026-06-15
616

717
### Added

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ The agent uses the directory you launched it from as its working directory — `
6262

6363
### Interactive features
6464

65-
- **Plan Mode** — press **shift+tab** to toggle (a `◆ PLAN MODE` indicator shows below the input). Submit a request and little-coder researches it with sub-coders, asks you 1-3 clarifying questions (each with suggested answers and a free-text option), then writes a plan in the chat instead of editing anything. **Esc** cancels a plan mid-run. (shift+tab used to cycle the thinking level — that's now **alt+t**.)
65+
- **Plan Mode** — press **alt+p** to toggle (a `◆ PLAN MODE` indicator shows below the input). Submit a request and little-coder researches it with sub-coders, asks you 1-3 clarifying questions (each with suggested answers and a free-text option), then writes a plan in the chat instead of editing anything. **Esc** cancels a plan mid-run. (**shift+tab** stays pi's thinking-level cycle.)
6666
- **Prompt history** — from an empty input, **** recalls your recent prompts (most-recent first), **** walks forward. History persists across sessions, so a fresh session can recall prompts from earlier runs.
6767
- **Sub-coders (`dispatch`)** — little-coder can spawn isolated child sessions to research a question (read the repo + browse online, read-only) and report back concisely, without cluttering the main conversation. A live panel above the input tracks them. Tune parallelism with `LITTLE_CODER_SUBCODER_CONCURRENCY` (default 2).
6868
- **Sessions** — each session is auto-named from your first prompt (rename with `/name`) and shown in the terminal tab title. Use `/resume` to list and reopen past sessions for the current directory.
@@ -342,7 +342,7 @@ little-coder/
342342
│ ├── settings.json # per-model profiles + benchmark_overrides (terminal_bench, gaia)
343343
│ └── extensions/ # 27 TypeScript extensions, auto-discovered by pi
344344
│ ├── branding/ # little-coder startup header + terminal title + session auto-naming
345-
│ ├── plan-mode/ # shift+tab "research → ask → plan" flow (sub-coders + clarifying questions → written plan)
345+
│ ├── plan-mode/ # alt+p "research → ask → plan" flow (sub-coders + clarifying questions → written plan)
346346
│ ├── subagent/ # `dispatch` tool: isolated read/browse-only sub-coders + live tracker (spawn.ts engine)
347347
│ ├── prompt-history/ # up-arrow recall of recent prompts (from an empty input)
348348
│ ├── llama-cpp-provider/ # data-driven provider registration from models.json — ships llamacpp, ollama, lmstudio (+ user override file)

bin/little-coder.mjs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
mkdirSync,
1010
readdirSync,
1111
readFileSync,
12+
rmSync,
1213
statSync,
1314
writeFileSync,
1415
} from "node:fs";
@@ -244,32 +245,31 @@ if (!isSubagent) try {
244245
writeFileSync(globalSettingsPath, JSON.stringify(globalSettings, null, 2));
245246
}
246247

247-
// ---- 8b. Free shift+tab for Plan Mode ----
248-
// little-coder binds shift+tab to its plan-mode toggle (the plan-mode
249-
// extension registers it). But shift+tab is pi's built-in "cycle thinking
250-
// level", and pi (>= 0.79) refuses an extension shortcut that collides with a
251-
// RESERVED built-in — it skips it with an "[Extension issues]" warning. pi
252-
// builds its conflict map from the *resolved* keybindings, so moving the
253-
// thinking-cycle action to another key in the user keybindings file removes
254-
// shift+tab from that map and lets the extension claim it. We rebind the
255-
// cycle to alt+t (the key plan-mode used to register itself) — only when the
256-
// user hasn't already chosen their own binding for it, so a real user
257-
// customization always wins. Non-destructive: every other binding is left
258-
// untouched.
248+
// ---- 8b. One-time cleanup of the v1.9.0 keybinding rewrite ----
249+
// v1.9.0 wrote `app.thinking.cycle: "alt+t"` into ~/.pi/agent/keybindings.json
250+
// so the plan-mode extension could claim shift+tab (issue #47). Plan mode now
251+
// lives on alt+p, so shift+tab should go back to pi's default thinking-cycle
252+
// binding — but only if the value is *exactly* the one we wrote. A user who
253+
// chose their own binding (anything ≠ "alt+t") wins.
259254
const keybindingsPath = join(agentDir, "keybindings.json");
260-
let keybindings = {};
261255
if (existsSync(keybindingsPath)) {
262256
try {
263257
const parsed = JSON.parse(readFileSync(keybindingsPath, "utf-8"));
264-
if (parsed && typeof parsed === "object") keybindings = parsed;
258+
if (parsed && typeof parsed === "object" && parsed["app.thinking.cycle"] === "alt+t") {
259+
delete parsed["app.thinking.cycle"];
260+
if (Object.keys(parsed).length === 0) {
261+
// Don't leave an empty {} sitting around — remove the file so pi
262+
// reads its defaults cleanly.
263+
rmSync(keybindingsPath);
264+
} else {
265+
writeFileSync(keybindingsPath, JSON.stringify(parsed, null, 2));
266+
}
267+
}
265268
} catch {
266-
keybindings = {};
269+
// Corrupted JSON or unreadable — leave it alone; pi will surface its own error.
267270
}
268271
}
269-
if (keybindings["app.thinking.cycle"] === undefined) {
270-
keybindings["app.thinking.cycle"] = "alt+t";
271-
writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2));
272-
}
272+
273273
} catch {
274274
// Best-effort. If we can't write the settings (read-only HOME, etc.) pi
275275
// falls back to its built-in defaults — the [Extensions] block will show

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "little-coder",
3-
"version": "1.9.0",
3+
"version": "1.9.1",
44
"description": "A pi-based coding agent optimized for small local language models. Reproduces the whitepaper's scaffold-model-fit adaptations as pi extensions.",
55
"homepage": "https://github.com/itayinbarr/little-coder",
66
"repository": {

0 commit comments

Comments
 (0)