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 -| | | -|---|---| -| ![Boot](docs/assets/workbench-boot.gif) | ![Loaded](docs/assets/workbench-loaded.gif) | -| App booting, workspaces populate | The workbench, fully loaded | +![App booting, workspaces populate](docs/assets/workbench-boot.png) + +App booting, workspaces populate. + +![The workbench, fully loaded](docs/assets/workbench-loaded.png) + +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{inner}\n\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 {