pigiDownload
Fix the tree header overlap
Session tree
Toggle terminal⌘ J
the tree header overlaps the row underneath
13:52
Thinking2s
The band is sticky and zero-height, so it paints over the row instead of taking its space.
readsrc/renderer/src/components/sessionTree/SessionTreePinnedBand.tsx
164      <div className="sticky top-0 z-20 h-0" aria-hidden="true" data-testid="session-tree-pinned">
0.1s

It paints over the row instead of moving it: the band is an overlay, and nothing reserves its height.

reserve it then
13:58
editsrc/renderer/src/components/sessionTree/SessionTreePinnedBand.tsx
-164 className="sticky top-0 z-20 h-0"
+164 className="sticky top-0 z-20 h-[var(--pinned-band)]"
0.4s

The band holds its own height now, so the rows move up by exactly that much while one is pinned.

13:58
Ask for follow-up changes
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
01

Your pi, unchanged

It is the pi you already have. Models, skills, extensions and sessions are the ones already on the machine, read from pi’s own configuration and session directory — no import step, no second setup, nothing to keep in sync.

02

View modes

One transcript, three densities. Minimal keeps the timer and the answer, Compact folds consecutive read-only calls into one line, and Show All is the native pi log — pick one from the filter menu and the same turn is drawn three ways.

03

Instant session switch

A long session is not a loading screen. Sessions open as fast as the window does, parsed in their own process and drawn as a windowed list that measures a row once — and the minimap takes you anywhere in it.

04

Fork

Branch off any message. Forking copies the conversation up to that point into a chat of its own, with the same model and thinking level; the original stays where it was, and the fork is listed beneath it.

05

Tree

Rewind without losing what you had. Going back moves the session — nothing is deleted and nothing has to be summarised, every branch is still in the tree, and the ? beside its search field says what a jump really does.

06

Session switcher

Every session is one line away. One field over all of pigi — every session, in every project, on Ctrl + R — and the letters you typed are marked in the titles, so you can see why a row is there.

07

Terminal

A shell in the same window. ⌘J opens a real terminal along the bottom, already in the project the agent is working in: run the test it just broke without leaving the conversation.

Bump electron-builder
Session tree
Toggle terminal⌘ J
the release DMG has been unsigned since the electron-builder bump
09:05
Thinking2s
The signing identity is in the config, so the build is finding the config and not the keychain. The bump moved the identity lookup behind an environment flag.
$rg -n 'identity|notarize' electron-builder.yml
14   identity: Developer ID Application: Earendil Inc. (7X2K4M9QLB)
22 notarize: true
0.1s

The identity is still in the config; the bump resolves it from the environment now, and there is nothing exported in the release job.

pin the version that still reads the config
09:12
$npm view electron-builder version
26.0.12
0.4s

Pinned to 26.0.12, which reads the identity from the config again, and left the version in the release skill so the next bump checks it before tagging.

is the DMG reproducible from this commit
09:18
$git status --short
0.1s

Clean tree, and the lockfile is the one the tag will build from.

09:18
/compactCompact session context
/reloadReload extensions, skills, prompts, settings and context files
/treeBrowse session branches
/code-review
/plannotator
/
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
The transcript flickers
Session tree
Toggle terminal⌘ J
Ask for follow-up changes
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
Terminal panel resize jank
Session tree
Toggle terminal⌘ J
the terminal panel drops frames while it animates open
10:44
Thinking2s
Two things resize on every frame: the panel's height and the transcript's viewport. Each of those frames re-measures the visible rows.
$rg -n 'panelHeight|setPanelHeight' src/renderer/src
TerminalPanel.tsx:41:  const [panelHeight, setPanelHeight] = useState(0);
TerminalPanel.tsx:88:      setPanelHeight(event.contentRect.height);
0.2s

The panel's height is React state, so the open animation re-renders the whole tree sixty times a second and the transcript re-measures its rows on every one of those frames.

can you scope the resize observer to the panel instead of the window
10:49
$git log --oneline -3 -- src/components/terminal/TerminalPanel.tsx
a678ff1 fix: hold a version’s header in place at the top
62d9bc3 fix: keep the session tree from blanking the window
0.1s

Done. The observer is on the panel element now, and the height it reports goes straight to a CSS custom property. The gesture writes one value per frame and the browser paints it on the compositor — React never hears about it.

what does the transcript do while that gesture is running
10:53
Thinking2s
The virtualizer needs to know the viewport height to decide how many rows to render. If the height changes every frame, it re-decides every frame.

It re-decides which rows to keep mounted, once per frame. Measured heights are cached, so it is not re-measuring — but it is re-rendering the window of rows, and that is where the frames go.

ok, do that. also check whether the minimap recomputes widths
11:02
$rg -n 'maxLineWidth' src/renderer/src/components/transcript/
UserMessageMiniMap.tsx:78:  const [maxLineWidth, setMaxLineWidth] = useState(
UserMessageMiniMap.tsx:82:  useEffect(() => {
UserMessageMiniMap.tsx:86:    const observer = new ResizeObserver(([entry]) => {
0.2s

It does — a `ResizeObserver` on the transcript container, writing state, on every frame of the gesture. Same shape of problem. The fix there is the same: publish the clamped width, and stop when the value has not changed.

does this affect the session tree too
11:08

Different mechanism. The tree draws a windowed list as well, but its rows are a fixed height, so the window is arithmetic rather than measurement — nothing to re-measure when the window resizes, only a `scrollTop` to re-read.

the tree preview card is also lagging when I move the pointer fast
11:14
Thinking2s
The card opens on a timer and closes on a timer. Moving fast across rows repeatedly restarts both, and each restart re-renders 380px of message preview.

The open delay restarts on every row, which is correct, but the card re-renders its body on each open. Debouncing the body to the row the pointer actually settles on removes the churn.

let's not ship that. revert the preview change for now
11:19
$git restore src/components/sessionTree/SessionTreeList.tsx
0.1s

Reverted. The panel and minimap changes stay; the preview card goes back to what shipped in 0.4.11 and I will redo it with the settle measurement in a separate pass.

good. run the tests before you commit
11:24
$npm test
 ✓ src/lib/sessionTreeLayout.test.ts (31 tests) 8ms
 ✓ src/lib/measuredRowHeights.test.ts (9 tests) 4ms
 Test Files  2 passed (2)
      Tests  40 passed (40)
4.1s

Forty tests, green. Ready to commit whenever you are.

11:24
Ask for follow-up changes
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
Keep the session tree from blanking
Session tree
Toggle terminal⌘ J
does the tree keep a fork under the session it came from
10:51
$rg -n "depth" src/renderer/src/lib/sessionLineage.ts
31 export function lineageDepth(parentPath: string | null): number {
54   const isLastChild = index === siblings.length - 1;
0.1s

It does. Depth is read from the session file, so a fork is drawn under its parent wherever it is opened from.

the tree jumps to the wrong row when I reopen it
10:58
Thinking2s
The scroll position is restored before the rows exist, so the browser clamps it to a list that is still empty.

It restores before the first window of rows is laid out, and a scroll into an empty list is clamped to nothing. Restored after the first frame, it lands where it left.

the tree blanks the window when a session is long
11:02
Thinking2s
Every row is measured on mount, so a session with thousands of entries spends the first frames laying all of them out.
readsrc/renderer/src/components/sessionTree/SessionTreeList.tsx
86  * Row heights are constants and headers are measured, so
0.2s

Rows are a fixed height and the list draws a window over them, so it never measures the whole tree.

and the header should stay put while the rows scroll
11:05Fork this msg in new session
Ask for follow-up changes
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
Add the chat minimap
Session tree
Toggle terminal⌘ J
Add a minimap to the transcript
09:12
Thinking2s
One bar per user message, sized by how long the message is.
readsrc/components/transcript/UserMessageMiniMap.tsx
78   const [maxLineWidth, setMaxLineWidth] = useState(
0.1s

One bar per user message. Here is the plan.

make the bars scale with length
09:14

Width now tracks the message length, clamped so a two-word message is still a target.

also show a timestamp on hover
09:18

Added — the list beside the bars reads the message and its time.

and dim the bars that are not active
09:21

Only the bar nearest the middle of the viewport is lit now.

09:21
Ask for follow-up changes
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
Add the chat minimap
Session tree
Toggle terminal⌘ J
Add a minimap to the transcript
09:12
Thinking2s
One bar per user message, sized by how long the message is.
readsrc/components/transcript/UserMessageMiniMap.tsx
78   const [maxLineWidth, setMaxLineWidth] = useState(
0.1s

One bar per user message. Here is the plan.

make the bars scale with length
09:14

Width now tracks the message length, clamped so a two-word message is still a target.

also show a timestamp on hover
09:18

Added — the list beside the bars reads the message and its time.

and dim the bars that are not active
09:21

Only the bar nearest the middle of the viewport is lit now.

09:21
Ask for follow-up changes
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
Fix the tree header overlap
Session tree
Toggle terminal⌘ J
the tree header overlaps the row underneath
13:52
Thinking2s
The band is sticky and zero-height, so it paints over the row instead of taking its space.
readsrc/renderer/src/components/sessionTree/SessionTreePinnedBand.tsx
164      <div className="sticky top-0 z-20 h-0" aria-hidden="true" data-testid="session-tree-pinned">
0.1s

It paints over the row instead of moving it: the band is an overlay, and nothing reserves its height.

reserve it then
13:58
editsrc/renderer/src/components/sessionTree/SessionTreePinnedBand.tsx
-164 className="sticky top-0 z-20 h-0"
+164 className="sticky top-0 z-20 h-[var(--pinned-band)]"
0.4s

The band holds its own height now, so the rows move up by exactly that much while one is pinned.

run the suite before you commit
15:19
$npm test
 Test Files  3 passed (3)
      Tests  49 passed (49)
4.1s

Green. The panel is open on the run if you want the full output.

15:19
Ask for follow-up changes
DeepSeek V4.1 Flashmax
main1.2%/1000k (auto)
15:19 · ~/Programs/pigi
-> git status --short
M src/components/sessionTree/SessionTreeList.tsx
M src/components/transcript/MessageList.tsx
-> npm test
✓src/lib/sessionTreeLayout.test.ts(31 tests) 8ms
✓src/lib/sessionLineage.test.ts(12 tests) 4ms
✓src/lib/readGrouping.test.ts(6 tests) 3ms
Test Files 3 passed (3)
Tests 49 passed (49)
15:20 · ~/Programs/pigi
->