---
status: resolved
parent: "[[057-pi-ui-customization-map]]"
blocked-by:
  - "[[062-pi-ui-quota-status-prototype]]"
  - "[[067-pi-ui-flex-spacer-bottom-anchor-prototype]]"
resolved-at: "2026-08-01T08:20:00-04:00"
tags:
  - ticket/task/afk
---

# Specify permanent Pi UI customization

## Question

Turn the resolved research and prototype verdicts into an implementation-ready specification for the permanent dotfiles-managed Pi UI customization, including what to build, what to leave out, and how to verify it after reload.

## Decision

Build the permanent customization as two pieces:

1. A dotfiles-managed Pi extension that owns the compact status surface.
2. A small local Pi layout patch that inserts flexible space above the prompt area when the TUI stack is shorter than the terminal.

Do not try to solve bottom anchoring with an extension spacer widget.
The prototype showed the correct behavior comes from the layout layer, not from guessing terminal height in a widget.

Do not use `terminal.clearOnShrink` as the primary fix.
The flex-spacer layout makes the prompt area continue to occupy the terminal bottom after transient autocomplete rows disappear.
`terminal.clearOnShrink` can remain a separate rendering knob for unrelated stale-row cases.

## Compact status extension specification

The extension should be installed through the dotfiles Pi module under the managed Pi extension directory.
The throwaway file at `~/.pi/agent/extensions/compact-status-prototype.ts` is not the deployment target.

The extension should use in-code defaults only.
It should not write a default config file.

The extension should hide the built-in footer by default with an empty custom footer renderer.
It should place the compact status widget above the editor.

The status widget has three rows:

```text
󰉋 ~/dotfiles 󰘬 main                                             gpt-5.5 • medium
▃▃▃▃▃▃▃▃▃▃▃▃───────────────────────────────────────────────────────────────
▔▔▔────────────────────────────────────────────────────────────────────────
```

Row 1 shows location and runtime state.
The left side is a visual cwd and branch indicator using `󰉋 <short-cwd> 󰘬 <branch>`.
The right side is `<model> • <thinking>`.
Use the session cwd when available, not `process.cwd()` as the primary source.
Use the current git branch when available.
If no branch exists, omit the branch segment.

Row 2 is the context-window usage bar.
It is unlabeled.
It uses full width.
It uses the lower three-eighths block glyph `▃`.
It fills by `ctx.getContextUsage().percent`.
It uses the theme success color under 70%, warning at 70%, and error at 90%.
The unfilled track uses the dim color.
If context usage is unknown, render a muted full-width track rather than inventing a percentage.
Do not show numeric context usage in the default status surface.
Do not attempt exact current-context category breakdown in the permanent default, because Pi does not expose that exact public data.

Row 3 is the ChatGPT Codex weekly quota consumed bar.
It is unlabeled.
It uses full width.
It uses the upper one-eighth block glyph `▔`.
It fills by consumed weekly quota, meaning `100 - weeklyRemaining`.
It starts empty when quota is unused.
It uses the theme border blue below 50% consumed so it contrasts with the green context bar.
It uses warning at 50% and error at 80%.
The unfilled track uses the dim color.
If quota is unknown, render a muted full-width track rather than inventing a percentage.

The extension should provide `/codex-quota`.
The command forces a quota refresh and shows a notification with normalized non-secret details.
The notification may include weekly remaining and any short-window value returned by the endpoint.
The default status surface should still show only the bars.

The extension may provide a development toggle command only if useful during implementation.
The finished default behavior should not require cycling modes.

## Quota data specification

The quota source is Pi's local OpenAI Codex OAuth credential at `~/.pi/agent/auth.json`, provider key `openai-codex`.
The extension must read only the fields it needs.
It must not log, display, or persist the access token, refresh token, account id, or raw response body.
It must not send OpenAI API keys to ChatGPT subscription quota endpoints.

The primary endpoints are:

```text
https://chatgpt.com/backend-api/wham/usage
https://chatgpt.com/backend-api/codex/usage
```

Requests use `Authorization: Bearer <access-token>`.
Requests include `ChatGPT-Account-Id` when the local OAuth credential has an account id.

Parsing must be defensive.
Accept snake_case and camelCase window fields.
Recognize the weekly window by approximately 604,800 seconds or by week-like names if duration is unavailable.
Recognize a short window only for the `/codex-quota` notification or fallback state.
The default status bar should prefer weekly quota.

Cache only normalized non-secret quota state in memory.
A five-minute refresh interval is acceptable for the status widget.
Manual `/codex-quota` refresh bypasses the freshness check.
Network errors, missing credentials, expired credentials, and unrecognized responses should produce an unknown/error state without throwing extension-load errors.

Do not add an app-server fallback in the first permanent version unless direct endpoint parsing proves unreliable during implementation.
The researched Codex app-server `account/rateLimits/read` path remains the preferred fallback if a fallback becomes necessary.

## Flex-spacer layout patch specification

Patch Pi's interactive TUI layout so normal content and prompt-area content are rendered as two groups.

Flow content:

- header container
- loaded resources container
- chat transcript container

Pinned prompt-area content:

- pending messages container
- terminal log container, if present in the installed Pi version
- status container
- above-editor widget container
- editor container
- below-editor widget container
- footer

During render, compute the rendered line count of both groups.
If `flowLines.length + pinnedLines.length` is less than the terminal row count, insert blank spacer rows between the groups.
If the combined line count is greater than or equal to the terminal row count, insert no spacer.

The patch should preserve the current visual behavior for long conversations.
It should only move spare empty space from below the editor to above the prompt area in underfilled screens.

The patch should recompute on each render and terminal resize.
It must not accumulate spacer rows as state.

The patch should keep autocomplete behavior otherwise unchanged.
Autocomplete may still render below the editor in the first permanent version.
Because the flex spacer fills the terminal, closing autocomplete should not leave the prompt visually floating above a blank bottom gap.

The patch should be implemented as a small local Pi package patch or upstreamable patch.
It should not be a runtime extension hack.

## Tool lookup hardening

The flex-spacer prototype exposed a NixOS-specific Pi tool lookup issue.
Pi can select `~/.pi/agent/bin/fd` before `PATH`, and that downloaded generic Linux binary can fail on NixOS with the stub-ld error.

The permanent Pi patch should also harden tool lookup if the local patch touches Pi internals.
A local tool path should count as available only if running `<tool> --version` exits with status 0.
If the local tool fails, Pi should continue to system `PATH`, where the Nix wrapper provides working `fd` and `rg`.

This hardening is not a UI feature, but it prevents `@` autocomplete from breaking under the patched Pi package.

## Dotfiles deployment specification

The finished extension must be promoted into the dotfiles Pi module's managed extension source tree.
It must not remain only under `~/.pi/agent/extensions`.

The finished Pi patch must be delivered through the flake-managed Pi package path.
The exact Nix module structure can be chosen during implementation, but the result should make the ordinary `pi` command use the patched layout after rebuild.

Do not manage `~/.pi/agent/auth.json`.
Login credentials remain user state.

Do not add a generated config file for this feature.

## Out of scope

Do not replace Pi's whole TUI.
Do not maintain an unrelated long-lived Pi fork.
Do not add opinionated quota policies or notifications beyond the manual `/codex-quota` command and passive status bar.
Do not display quota numbers in the default compact status surface.
Do not attempt to make exact token-category context breakdown claims from cumulative session usage.

## Verification

After implementation, run the repository's normal verification seam:

```bash
nix flake check
```

After switching to the new generation, launch normal Pi with:

```bash
pi
```

Verify after `/reload` that no extension load errors appear.

Verify the compact status visual shape:

- row 1 shows cwd/branch on the left and model/thinking on the right
- row 2 is an unlabeled `▃` context bar
- row 3 is an unlabeled `▔` Codex consumed weekly quota bar
- the built-in footer is hidden by default

Verify `/codex-quota`:

- it forces a refresh
- it shows a non-secret normalized notification
- it does not print tokens, account ids, or raw JSON
- it degrades to unknown/missing/error without crashing when credentials or network are unavailable

Verify flex-spacer behavior:

- a short conversation leaves spare rows above the status/editor area, not below the editor
- a long conversation behaves like stock Pi and does not insert visible spacer rows inside the transcript
- resizing the terminal recomputes the spacer
- `/` autocomplete and `@` autocomplete still work
- closing autocomplete leaves the editor visually bottom-aligned

## Follow-up implementation tickets

Implement this specification through:

- [[068-pi-ui-compact-status-extension-task]]
- [[070-pi-ui-flex-spacer-pi-patch-task]]
- [[071-pi-ui-final-deployment-verification-task]]
