diff --git a/README.md b/README.md
index 3ef969f..b320451 100644
--- a/README.md
+++ b/README.md
@@ -22,10 +22,13 @@ Daily coding-agent work means juggling repos, branches, and sessions, and the cu
## In action
-| | |
-|---|---|
-|  |  |
-| App booting, workspaces populate | The workbench, fully loaded |
+
+
+App booting, workspaces populate.
+
+
+
+The workbench, fully loaded.
## Install
diff --git a/assets/logo-editorial.svg b/assets/logo-editorial.svg
new file mode 100644
index 0000000..61ad06e
--- /dev/null
+++ b/assets/logo-editorial.svg
@@ -0,0 +1,13 @@
+
diff --git a/assets/logo-label.svg b/assets/logo-label.svg
new file mode 100644
index 0000000..9893343
--- /dev/null
+++ b/assets/logo-label.svg
@@ -0,0 +1,23 @@
+
diff --git a/assets/logo-wordmark.svg b/assets/logo-wordmark.svg
new file mode 100644
index 0000000..1d9b8ed
--- /dev/null
+++ b/assets/logo-wordmark.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/mark-accent.svg b/assets/mark-accent.svg
new file mode 100644
index 0000000..72b8b68
--- /dev/null
+++ b/assets/mark-accent.svg
@@ -0,0 +1,16 @@
+
diff --git a/assets/mark-blueprint.svg b/assets/mark-blueprint.svg
new file mode 100644
index 0000000..c21dd1d
--- /dev/null
+++ b/assets/mark-blueprint.svg
@@ -0,0 +1,43 @@
+
diff --git a/assets/mark-plate.svg b/assets/mark-plate.svg
new file mode 100644
index 0000000..248e81d
--- /dev/null
+++ b/assets/mark-plate.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/mark-seal.svg b/assets/mark-seal.svg
new file mode 100644
index 0000000..22b519a
--- /dev/null
+++ b/assets/mark-seal.svg
@@ -0,0 +1,97 @@
+
diff --git a/assets/mark-stencil.svg b/assets/mark-stencil.svg
new file mode 100644
index 0000000..c756915
--- /dev/null
+++ b/assets/mark-stencil.svg
@@ -0,0 +1,12 @@
+
diff --git a/assets/mark-warm.svg b/assets/mark-warm.svg
new file mode 100644
index 0000000..d01e589
--- /dev/null
+++ b/assets/mark-warm.svg
@@ -0,0 +1,16 @@
+
diff --git a/assets/mascot.svg b/assets/mascot.svg
new file mode 100644
index 0000000..210bd9f
--- /dev/null
+++ b/assets/mascot.svg
@@ -0,0 +1,27 @@
+
diff --git a/docs/adr/0002-pierre-diffs-renderer.md b/docs/adr/0002-pierre-diffs-renderer.md
index 7c00a18..be73b2f 100644
--- a/docs/adr/0002-pierre-diffs-renderer.md
+++ b/docs/adr/0002-pierre-diffs-renderer.md
@@ -9,5 +9,4 @@
- Diff renderer scales to 10k+ lines via per-line virtualisation in Shadow DOM.
- Same engine as OpenChamber / Claude Code / Codex / Cursor — visual parity for shared screenshots.
- Adds ~1MB of language packs to the bundle; acceptable for an agent workbench, with `chunks larger than 500kB` warning expected until we code-split Shiki languages.
- - Plumbs into future "review session" views where merge-conflict UI will reuse the same renderer.
- - Until file content plumbed from the main process, the renderer ships a `Fig. 02 | Pierre diff probe` sample in the empty state.
\ No newline at end of file
+ - Plumbs into future "review session" views where merge-conflict UI will reuse the same renderer.
\ No newline at end of file
diff --git a/docs/assets/banner-bg.svg b/docs/assets/banner-bg.svg
index 676588a..85017d2 100644
--- a/docs/assets/banner-bg.svg
+++ b/docs/assets/banner-bg.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/assets/banner.png b/docs/assets/banner.png
index 105334c..be591b1 100644
Binary files a/docs/assets/banner.png and b/docs/assets/banner.png differ
diff --git a/docs/assets/workbench-boot.gif b/docs/assets/workbench-boot.gif
deleted file mode 100644
index fa540da..0000000
Binary files a/docs/assets/workbench-boot.gif and /dev/null differ
diff --git a/docs/assets/workbench-boot.png b/docs/assets/workbench-boot.png
new file mode 100644
index 0000000..5e04a6a
Binary files /dev/null and b/docs/assets/workbench-boot.png differ
diff --git a/docs/assets/workbench-loaded.gif b/docs/assets/workbench-loaded.gif
deleted file mode 100644
index a8d0499..0000000
Binary files a/docs/assets/workbench-loaded.gif and /dev/null differ
diff --git a/docs/assets/workbench-loaded.png b/docs/assets/workbench-loaded.png
new file mode 100644
index 0000000..ba99cdd
Binary files /dev/null and b/docs/assets/workbench-loaded.png differ
diff --git a/scripts/build-banner.py b/scripts/build-banner.py
index 699248f..9156f15 100644
--- a/scripts/build-banner.py
+++ b/scripts/build-banner.py
@@ -31,10 +31,11 @@ def build_bg_svg():
''
''
''
- ''
+ ''
''
''
- ''
+ ''
+ ''
''
''
f''
@@ -76,7 +77,7 @@ def main():
"--export-filename",
str(lockup),
"--export-width",
- "760",
+ "920",
],
check=True,
)
diff --git a/scripts/build-identity.py b/scripts/build-identity.py
new file mode 100644
index 0000000..d42dfb3
--- /dev/null
+++ b/scripts/build-identity.py
@@ -0,0 +1,345 @@
+"""Extra pidex marks, lockups, and the desk mascot.
+
+The cube emblem stays the primary mark. These are the other ways it is drawn:
+a plate, a stencil, a blueprint, a seal, two ink styles, type lockups, and
+the mascot (the cube, with the square dot standing beside it).
+
+Outputs live in assets/ and are not the files build-logo.py owns.
+"""
+
+from __future__ import annotations
+
+import importlib.util
+import math
+from pathlib import Path
+
+from fontTools.pens.svgPathPen import SVGPathPen
+from fontTools.ttLib import TTFont
+
+REPO = Path(__file__).resolve().parent.parent
+ASSETS = REPO / "assets"
+DEPARTURE = REPO / "src/renderer/assets/fonts/DepartureMono-Regular.woff2"
+
+spec = importlib.util.spec_from_file_location("build_logo", REPO / "scripts/build-logo.py")
+logo = importlib.util.module_from_spec(spec)
+assert spec.loader is not None
+spec.loader.exec_module(logo)
+
+INK = logo.INK
+EDGE = logo.EDGE
+ACCENT = "#6a9fcc"
+DEEP = "#0d1116"
+WARM = "#e1b06e"
+WARM_INK = "#13110f"
+
+
+def svg(inner: str, view: str) -> str:
+ return f'\n'
+
+
+def write(name: str, body: str) -> None:
+ path = ASSETS / name
+ path.write_text(body)
+ print(f"wrote {path}")
+
+
+def pi_group(x: float, y: float, size: float, fill: str) -> str:
+ scale = size / logo.PI_SPAN
+ return (
+ f'\n'
+ f' \n'
+ f' \n'
+ ""
+ )
+
+
+def plate() -> str:
+ return svg(
+ "\n".join(
+ [
+ f'',
+ pi_group(22, 22, 116, INK),
+ ]
+ ),
+ "0 0 160 160",
+ )
+
+
+def stencil() -> str:
+ scale = 116 / logo.PI_SPAN
+ return svg(
+ "\n".join(
+ [
+ "",
+ ' ',
+ ' ',
+ f' ',
+ f' ',
+ f' ',
+ " ",
+ " ",
+ "",
+ '',
+ ]
+ ),
+ "0 0 160 160",
+ )
+
+
+def blueprint() -> str:
+ lines = []
+ for i in range(0, 161, 8):
+ major = i % 40 == 0
+ opacity = "0.55" if major else "0.16"
+ lines.append(
+ f''
+ )
+ corners = []
+ for x, y, sx, sy in ((10, 10, 1, 1), (150, 10, -1, 1), (10, 150, 1, -1), (150, 150, -1, -1)):
+ corners.append(
+ f''
+ )
+ cube = logo.cube_markup(logo.MONO, "pi-face-blueprint")
+ return svg(
+ "\n".join(
+ [
+ *lines,
+ *corners,
+ '',
+ cube,
+ "",
+ ]
+ ),
+ "0 0 160 160",
+ )
+
+
+def outline_glyphs(font_path: Path, text: str, size: float) -> list[tuple[str, float]]:
+ font = TTFont(font_path)
+ font.flavor = None
+ glyphset = font.getGlyphSet()
+ cmap = font.getBestCmap()
+ upem = font["head"].unitsPerEm
+ scale = size / upem
+ hmtx = font["hmtx"]
+ runs: list[tuple[str, float]] = []
+ for char in text:
+ name = cmap[ord(char)]
+ pen = SVGPathPen(glyphset)
+ glyphset[name].draw(pen)
+ advance, _ = hmtx[name]
+ runs.append((pen.getCommands(), advance * scale))
+ return runs
+
+
+def seal_with_upem(upem: int) -> str:
+ ticks = []
+ for i in range(72):
+ angle = math.radians(i * 5 - 90)
+ inner = 64 if i % 6 else 58
+ outer = 72
+ x0, y0 = 90 + inner * math.cos(angle), 90 + inner * math.sin(angle)
+ x1, y1 = 90 + outer * math.cos(angle), 90 + outer * math.sin(angle)
+ ticks.append(
+ f''
+ )
+ size = 13.0
+ letters = outline_glyphs(DEPARTURE, "PIDEX", size)
+ gap = size * 0.42
+ width = sum(advance for _, advance in letters) + gap * (len(letters) - 1)
+ radius = 52
+ cursor = -width / 2
+ glyphs = []
+ scale = size / upem
+ for commands, advance in letters:
+ mid = cursor + advance / 2
+ theta = mid / radius
+ x = 90 + radius * math.sin(theta)
+ y = 90 - radius * math.cos(theta)
+ deg = math.degrees(theta)
+ if commands:
+ glyphs.append(
+ f''
+ )
+ cursor += advance + gap
+ cube = logo.cube_markup(logo.PRIMARY, "pi-face-seal")
+ return svg(
+ "\n".join(
+ [
+ f'',
+ f'',
+ *ticks,
+ *glyphs,
+ '',
+ cube,
+ "",
+ ]
+ ),
+ "0 0 180 180",
+ )
+
+
+def wordmark() -> str:
+ raw, box = logo.wordmark_markup(0)
+ pad = 2
+ view = f"{box[0] - pad:.2f} {box[1] - pad:.2f} {box[2] - box[0] + pad * 2:.2f} {box[3] - box[1] + pad * 2:.2f}"
+ return svg(logo.shift_wordmark(raw, 0, 0), view)
+
+
+def editorial() -> str:
+ raw, box = logo.wordmark_markup(0)
+ word_h = box[3] - box[1]
+ pi = word_h * 1.05
+ gap = 16
+ dx = pi + gap - box[0]
+ dy = -box[1]
+ pad = 2
+ width = pi + gap + (box[2] - box[0]) + pad * 2
+ height = max(pi, word_h) + pad * 2
+ return svg(
+ "\n".join(
+ [
+ pi_group(pad, pad + (height - pad * 2 - pi) / 2, pi, INK),
+ logo.shift_wordmark(raw, dx + pad, dy + pad),
+ ]
+ ),
+ f"0 0 {width:.2f} {height:.2f}",
+ )
+
+
+def label_lockup(upem: int) -> str:
+ size = 18.0
+ letters = outline_glyphs(DEPARTURE, "PIDEX", size)
+ gap = size * 0.22
+ width = sum(advance for _, advance in letters)
+ tracked = width + gap * (len(letters) - 1)
+ scale = size / upem
+ cursor = 0.0
+ glyphs = []
+ for commands, advance in letters:
+ if commands:
+ glyphs.append(
+ f''
+ )
+ cursor += advance + gap
+ cube_bottom = logo.CY + logo.DY + logo.E
+ cube_top = logo.CY - logo.DY
+ cube_right = logo.CX + logo.DX
+ cube_h = cube_bottom - cube_top
+ # Departure sits on the baseline. Cap height is roughly 0.7 of the em.
+ text_h = size * 0.72
+ text_y = cube_top + (cube_h + text_h) / 2
+ text_x = cube_right + 18
+ pad = 8
+ view_w = text_x + tracked + pad - (logo.CX - logo.DX - pad)
+ view_h = cube_h + pad * 2
+ return svg(
+ "\n".join(
+ [
+ logo.cube_markup(logo.PRIMARY, "pi-face-label"),
+ f'',
+ *glyphs,
+ "",
+ ]
+ ),
+ f"{logo.CX - logo.DX - pad:.2f} {cube_top - pad:.2f} {view_w:.2f} {view_h:.2f}",
+ )
+
+
+def iso_cube(cx: float, cy: float, e: float, top: str, right: str, left: str, edge: str) -> tuple[str, tuple[float, float, float, float]]:
+ dx = e * 0.8660254037844386
+ dy = e * 0.5
+
+ def xy(p: float, q: float) -> tuple[float, float]:
+ return (cx + q * dx, (cy - dy) + p * dy)
+
+ top_pts = [xy(0, 0), xy(1, 1), xy(2, 0), xy(1, -1)]
+ right_pts = [xy(1, 1), xy(2, 0), (cx, cy + dy + e), (cx + dx, cy + e)]
+ left_pts = [xy(1, -1), xy(2, 0), (cx, cy + dy + e), (cx - dx, cy + e)]
+
+ def poly(points: list[tuple[float, float]], fill: str) -> str:
+ body = " ".join(f"{x:.2f},{y:.2f}" for x, y in points)
+ return (
+ f''
+ )
+
+ markup = "\n".join([poly(left_pts, left), poly(right_pts, right), poly(top_pts, top)])
+ bounds = (cx - dx, cy - dy, cx + dx, cy + dy + e)
+ return markup, bounds
+
+
+def diamond(cx: float, cy: float, e: float, scale: float) -> list[tuple[float, float]]:
+ dx = e * 0.8660254037844386
+ dy = e * 0.5
+
+ def xy(p: float, q: float) -> tuple[float, float]:
+ return (cx + q * dx, (cy - dy) + p * dy)
+
+ corners = [(0, 0), (1, 1), (2, 0), (1, -1)]
+ return [xy(1 + (p - 1) * scale, q * scale) for p, q in corners]
+
+
+def mascot() -> str:
+ body, _ = iso_cube(78, 78, 78, logo.TOP, logo.RIGHT, logo.LEFT, EDGE)
+ dx = 78 * 0.8660254037844386
+ dy = 78 * 0.5
+ # Back tip of the top face is (cx, cy - dy). The Pi's top-left lands there.
+ b = dy / logo.PI_SPAN
+ ty = (78 - dy) - 2 * b * 165.29
+ pi_scale_matrix = (
+ f"matrix({dx / logo.PI_SPAN:.8f} {b:.8f} {-dx / logo.PI_SPAN:.8f} {b:.8f} 78.00 {ty:.2f})"
+ )
+ dot_cube, _ = iso_cube(168, 118, 36, logo.TOP, logo.RIGHT, logo.LEFT, EDGE)
+ dot = diamond(168, 118, 36, 0.5)
+ dot_pts = " ".join(f"{x:.2f},{y:.2f}" for x, y in dot)
+ left_foot, _ = iso_cube(52, 168, 20, "#243246", "#31475c", "#1a2432", EDGE)
+ right_foot, _ = iso_cube(104, 168, 20, "#243246", "#31475c", "#1a2432", EDGE)
+ return svg(
+ "\n".join(
+ [
+ '',
+ left_foot,
+ right_foot,
+ body,
+ "",
+ ' ',
+ f' ',
+ " ",
+ "",
+ '',
+ f' ',
+ f' ',
+ f' ',
+ " ",
+ "",
+ dot_cube,
+ f'',
+ ]
+ ),
+ "0 0 230 230",
+ )
+
+
+def main() -> None:
+ font = TTFont(DEPARTURE)
+ upem = font["head"].unitsPerEm
+ write("mark-plate.svg", plate())
+ write("mark-stencil.svg", stencil())
+ write("mark-blueprint.svg", blueprint())
+ write("mark-seal.svg", seal_with_upem(upem))
+ write("mark-accent.svg", logo.build_mark_svg(logo.Palette("#6a9fcc", "#4b607c", "#2c4058", "#d5e4f2", DEEP), "pi-face-accent"))
+ write(
+ "mark-warm.svg",
+ logo.build_mark_svg(logo.Palette(WARM, "#a56a42", "#6e4632", "#f4e4cf", WARM_INK), "pi-face-warm"),
+ )
+ write("logo-wordmark.svg", wordmark())
+ write("logo-editorial.svg", editorial())
+ write("logo-label.svg", label_lockup(upem))
+ write("mascot.svg", mascot())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 6071dcc..460b1e7 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -5,7 +5,6 @@ import { PidexMark } from './components/PidexMark';
import { FigureFrame } from './components/FigureFrame';
import { BracketButton } from './components/BracketButton';
import { Markdown } from './components/Markdown';
-import { PierreDiff } from './components/PierreDiff';
import { applyEvent, entriesFromMessages } from './timeline';
import type { Entry } from './timeline';
import type { SessionInfo, SlashCommand, WorkspaceInfo } from './api';
@@ -49,41 +48,6 @@ function ToolOutput({ text }: { text: string }) {
);
}
-const PIERRE_DIFF_SAMPLE = {
- filename: 'src/renderer/components/Markdown.tsx',
- oldFile: `import MarkdownIt from 'markdown-it';
-
-const md = new MarkdownIt({ html: false });
-
-type MarkdownProps = { text: string };
-
-function Markdown({ text }: MarkdownProps) {
- return
;
-}
-
-export { Markdown };
-`,
- newFile: `import MarkdownIt from 'markdown-it';
-import { useMemo } from 'react';
-
-const md = new MarkdownIt({ html: false, breaks: true, linkify: true });
-
-type MarkdownProps = { text: string; className?: string };
-
-function Markdown({ text, className }: MarkdownProps) {
- const html = useMemo(() => md.render(text), [text]);
- return (
-
- );
-}
-
-export { Markdown };
-`,
-};
-
function App() {
const [workspaces, setWorkspaces] = useState([]);
const [wsSessions, setWsSessions] = useState>({});
@@ -553,17 +517,6 @@ function App() {
Pick a session from the sidebar, or start a new one in the expanded workspace.
-
-
-
)}
diff --git a/src/renderer/app.css b/src/renderer/app.css
index fcc83c7..4868ad5 100644
--- a/src/renderer/app.css
+++ b/src/renderer/app.css
@@ -603,11 +603,6 @@
color: var(--faint);
}
-.empty-pane .probe {
- margin-top: var(--space-5);
- width: min(880px, 100%);
-}
-
/* Tool card ------------------------------------------------------------- */
.tool-card {