Repository navigation
v1.3.0
Added
- README "Upgrading from 1.2.x" section: branch-mode migration, re-running
xpo init,xpo boardnetwork binding and access token, JSON output changes, principal-first assignees. The Board section now documents the--hostaccess token (xpo-e60c26) xpo board --host <non-loopback>requires a per-run access token. It is printed on startup as…/?token=…and exchanged for anHttpOnly,SameSite=Laxsession cookie namedexponential-<sha256(token)[:8]>. Without the cookie,/api/*returns 401 and pages redirect to a new/authform; the web UI redirects there on any 401./healthzstays public, and loopback binds are unchanged (xpo-897f35)- Web UI bundles Inter Variable and JetBrains Mono Variable (latin + latin-ext woff2, embedded in the binary) instead of relying on locally installed fonts;
@font-facepinsascent-override/descent-override/line-gap-overrideso Linux and macOS lay out text identically. SIL OFL 1.1 license texts ship at/licenses/(xpo-bade50) SearchInputin the My Issues TopBar filters the active tab by title, ID or label; Backlog and My Issues share a newmatchesSearchpredicate, and Backlog search now ignores surrounding whitespace (xpo-5ed512)SearchInputin the Labels TopBar filters labels by name (case-insensitive substring), with a "No labels match" empty state (xpo-c6043f)unlinkMCP tool andxpo unlink <source> <target> -t <type>CLI command (with--json): remove one relationship between two issues, whichever side stores it, including links to deleted issues (xpo-9d5d00)- "Worked by" filter (Agent / Human) in the Backlog, My Issues and Inbox filter menus, backed by a new
assignee_viafield on issues in the CLI JSON, MCP and HTTP API (xpo-35fc16) - "Open" button in the issue detail top bar that opens the issue's local worktree in VS Code via a
vscode://file/…link; the path comes from a new local-onlyGET /api/local/issues/{id}/worktreeendpoint, whichxpo boardanswers itself even in remote/proxy mode (xpo-c4fbed) - Unified JSON I/O layer:
internal/jsonio/package as single source of truth for all JSON-serializable types shared between CLI and MCP (xpo-1a5504) xpo list --jsonoutputsjsonio.ListOutputenvelope (xpo-2e3060)xpo show --jsonoutputsjsonio.ShowOutputenvelope with children and archived status (xpo-b5d8f0)xpo comments --jsonoutputsjsonio.CommentsOutputenvelope (xpo-e43fff)xpo blockedwithout arguments lists all blocked issues;--jsonflag for JSON output (xpo-c3f7a0)xpo inbox --jsonoutputsjsonio.InboxOutputenvelope (xpo-d31d61)SearchInputcomponent added to Timeline TopBar (xpo-23529e)MenuFiltercomponent for filterable menus;bareprop onMenufor chrome-free mode inside existing containers (xpo-9a7907)- Digit keys (0-9) registered in Menu keyboard handler for number-key shortcuts (xpo-9a7907)
- Composable
Menu,MenuItem,SubMenu,MenuDivider,MenuLabelcomponent system with ARIA roles, keyboard navigation via the shortcut registry, safe-triangle diagonal movement, submenu close delay, and sub-menu flyout positioning; ContextMenu and FilterMenu refactored to compose from these primitives (xpo-de8d06) PopoverPanelcomponent for visual chrome in raw-content Popovers (xpo-de8d06)pointInTriangleutility for safe-triangle hover calculations (xpo-de8d06)popover-utilsextended withright-startplacement for sub-menu flyouts with left-flip and viewport clamping (xpo-de8d06)menu-utilspure functions for index navigation with wrapping/skip and shortcut matching (xpo-de8d06)Textcomponent for consistent typography primitives (xpo-e0334f)- Shared
Tabs<T>component with[/]keyboard cycling via the shortcut registry; Backlog and My Issues migrated, removing duplicated tab markup (xpo-d47781) - Control-priority shortcuts fold into the active view's group in the keyboard help overlay instead of creating separate sections (xpo-d47781)
- Keyboard system architecture design document (
design-docs/keyboard-system.md) (xpo-d47781) - Shared
SearchInputcomponent with/focus andEscclear via the shortcut registry; Backlog and Dependencies migrated (xpo-ef6602) - Shared
IconButtoncomponent for toolbar actions with built-in tooltip and active-indicator dot; 6 instances across Backlog, Board, Inbox, MyIssues, and Timeline migrated (xpo-4235c6) Popoverrefactored to anchor-ref positioning with placement options, 4-edge viewport clamping, and built-in click-outside/Escape dismissal; 17 consumer instances migrated,InlineDropdownrewritten on Popover (xpo-5d1c38)Buttoncomponent supportsforwardReffor ref-based popover anchoring (xpo-5d1c38)- Shared
FilterButtoncomponent bundling IconButton, FilterMenu,fshortcut, and active dot; Backlog, MyIssues, and Inbox migrated (xpo-e16962) xpo specCLI command for viewing and managing issue specs (xpo-d9d6f2)xpo walkthroughCLI command for viewing and managing issue walkthroughs (xpo-09a820)--with-specand--with-walkthroughflags onxpo showcommand and MCPshowtool to inline artifact content (xpo-68eb93)xpo artifact --jsoninput/output withjsonioenvelope (xpo-43d8c5)xpo start --jsoninput/output withjsonioenvelope (xpo-cdd060)xpo merge --jsoninput/output withjsonioenvelope (xpo-e19cfd)xpo link --jsoninput/output withjsonioenvelope (xpo-885f42)- Standardized JSON error output across all CLI
--jsoncommands — consistent{"error": ...}envelope on validation and runtime failures (xpo-d1a06b) Headingcomponent for consistent title and heading styling (xpo-250ddf)CountBadgecomponent for issue count badges (xpo-f27a3e)xpo doctor --fix: auto-fixes local state silently, shows change plan for committed files, offers replace/keep/diff for edited managed blocks (xpo-76e2a1)xpo doctor --strict: warnings exit non-zero for CI (xpo-76e2a1)xpo initflags:--yes,--prefix,--agents,--forcefor non-interactive/CI use (xpo-76e2a1)- Version stamping in managed block markers — integration version derived from blocks, not stored in config (xpo-76e2a1)
- Re-init intelligence: up-to-date one-liner, stale version diffs, downgrade protection, edited-block detection (xpo-76e2a1)
charmbracelet/huhdependency for multi-select and select prompts (xpo-76e2a1)- 53 new tests: managed blocks, health checks, doctor scenarios, version comparison (xpo-76e2a1)
xpo doctorreportsissues.dblines whosecreated_atdoes not strictly increase in file order (report-only) (xpo-e9a2eb)
Changed
make releaseis replaced bymake release-prep VERSION=x.y.z, which bumpsCLIVersionand dates the CHANGELOG section in the release issue's worktree without committing, andmake release-tag VERSION=x.y.z, which tags onmainafter the merge after checking the branch, a clean tree and the version, and does not push. It usesperl -piinstead of GNU-onlysed -i, so it works on macOS (xpo-e60c26)xpo start(CLI and MCP) is idempotent when run from inside the issue's existing worktree. It returns success with the same branch and worktree path, moves the issue to DOING if needed, and doesn't recreate the worktree or re-runworktree_setup. This lets an orchestrator start an issue and then let the agent inside the worktree callstartagain.--forcestill tears down and recreates the worktree (xpo-7325f7)- Web UI writes now go through TanStack Query mutations (
api/mutations.ts) instead of components calling the API and thenonRefresh(). Status, estimate, priority, label, assignee and cycle edits, plus Board and Backlog drags, show up immediately and roll back with a "Couldn't save: " toast if the server rejects them. A status cascade to sub-issues refetches issues once instead of once per child. Creating an issue, starting work, deleting, commenting and label edits now toast on failure instead of failing silently. TheonRefresh,patchIssueandonConfigLabelsChangeprops are gone (xpo-6e757d) - Web UI per-view data (cycles, cycle progress, dashboard metrics and activity, timeline, instances, and issue history, artifacts, worktree, commits, diffs and mergeability) now loads through shared TanStack Query hooks: requests are deduplicated across views, and cycles, dashboard and timeline refresh on server events instead of going stale until remounted. MergeView spins its Refresh icon while refreshing (at least one full turn on a click), then briefly shows a check, or an alert plus a "Couldn't refresh: " toast; a failed uncommitted-diff poll shows "Couldn't refresh, showing changes as of HH:MM" beside it instead of silently showing outdated changes (xpo-ec0480)
- Web UI restructured from a single
Appcomponent intoLayout→Sidebar+ViewRouter→ views. Views no longer receive props: they read issues, config, inbox and user from TanStack Query hooks, whose cache is refreshed by server events, and own their own tab, filter and sort state. Saved filters, sort and tabs keep their existing localStorage keys. Filter chips are rendered into the status bar by each view, the sidebar, routes, titles andgshortcuts all come from oneVIEWSregistry, and the Inbox list now usesViewContainer(xpo-de3808) - Issue detail prev/next (
j/k) now walks the order of the view the issue was opened from (Issues, Board, My Issues or a cycle), instead of always the Issues list; deep links and other views use the default sorted order (xpo-de3808) - Backlog rows and status headers no longer draw separator lines and have rounded backgrounds (xpo-c26f7b)
- All TopBar views except Inbox render through a shared
ViewContainershell (TopBar, optional header bars, and a full-width content area with the scrollbar at the view's right edge; width-limited views center amax-w-7xlor issue-detail column inside it). Scroll areas that change while visible (views, sidebars, MergeView panes and diffs, pickers, the command palette, scrolling menus) use a newscroll-stableutility: a forced, transparent track with a symmetricboth-edgesgutter, so content no longer shifts when a scrollbar appears in Chrome or Safari. The global.overflow-y-auto { overflow-y: scroll }override is gone, sooverflow-y-autobehaves normally again (xpo-c26f7b) - Links are bidirectional:
A blocks Balso meansB blocked_by A, whichever issue stores it.show(MCP, CLI, API, web) lists derived inverse links withderived: true.startandxpo drivehonour blockers stored on the other issue.linkreports a relationship already stored from the other side as existing.update { links }treats the list as the issue's full relationship set, so a link left out is removed from whichever side stores it (xpo-9d6609) - Assignees are principal-first: an issue is always assigned to a person, never an agent. Old assignments to agent identities (
Claude Code <agent@host.local>) resolve to the person the agent worked for, at read time, with no event-log rewrite. Avatars and assignee names always show the person; agent-written comments read "Claude Code on behalf of " (xpo-35fc16) startassigns the issue to the person the starter works for (the MCP/drive principal, or the CLI user); an issue held by someone else keeps its assignee unless--force(xpo-35fc16)xpo driverecords its events on behalf of the git-config user (xpo-35fc16)- CLI timeline and inbox lead with the person: "Nicolas (via Claude Code)" (xpo-35fc16)
xpo add --jsonnow usesjsonio.AddInputdirectly (droppinginputsshim), callsValidateCreatePayloadfor full validation parity with MCP, and emitsjsonio.AddOutputJSON instead of plain text (xpo-3847a5)xpo update --jsonnow usesjsonio.UpdateInputdirectly, callsValidateUpdatePayloadfor full validation parity with MCP, and emitsjsonio.UpdateOutputJSON instead of plain text;done,planned, andblocked <id>gain--jsonoutput (xpo-199282)xpo rationale --jsonnow outputsjsonio.RationaleOutputenvelope instead of raw search result (xpo-11e559)- Breaking:
xpo history --jsonnow outputs a single JSON envelope with rich timeline entries instead of JSONL; MCPhistorytool output upgraded to include payload, on_behalf_of, and source fields (xpo-f56ce4) internal/inputs/reduced to a backward-compat re-export shim overinternal/jsonio/; will be removed once CLI commands are migrated (xpo-1a5504)StatusPicker,PriorityPicker,EstimatePicker,LabelPickerrewritten as Menu + MenuItem compositions, eliminating duplicated keyboard handling, focus management, and CheckIcon definitions (xpo-9a7907)Popoveris now positioning-only; visual chrome moved toPopoverPanelorMenu(xpo-de8d06)ContextMenudecomposed from monolithic 470-line component into Menu/MenuItem/SubMenu composition (xpo-de8d06)FilterMenurebuilt with Menu/SubMenu; positioning fixed withuseLayoutEffect+queueMicrotask(xpo-de8d06, xpo-b5a70c)- Backlog and Board view option menus refactored from inline JSX to Menu compositions (xpo-de8d06)
- Menu items use compact sizing (
py-1.5) with three-tier visual hierarchy: secondary text, hover highlight, active highlight (xpo-de8d06) xpo initis now a unified wizard: prefix prompt → agent multi-select → terraform-style change plan → confirm → apply. Replaces the separateinit mcpandinit skillsubcommands (xpo-76e2a1)- Agent instruction files use managed blocks (
<!-- xpo:begin VERSION sha256:HASH -->) for safe refresh — only the xpo-owned section is replaced, user content is never touched (xpo-76e2a1) - Prefix stored without trailing dash (
paynotpay-); separator added at point of use. Existing configs normalized on load (xpo-76e2a1) xpo doctorreorganized into four sections: xpo, Project, Integrations, This machine (xpo-76e2a1)- Branded welcome message when running
xpoor any command outside a project directory (xpo-76e2a1) - Tagline defined once (
ui.Tagline) and used consistently across all CLI output (xpo-76e2a1) commentscommand refactored from rawstorage.ReadEvents()toClient.GetIssue()(xpo-e43fff)xpo comment --jsonrefactored to use shared MCPCommentToolInputschema viajsonio, with full validation parity (xpo-aa4529)- All CLI
--jsoncommands now resolve input IDs (short IDs, bare hashes) to canonical issue IDs before processing (xpo-db6d52) - Pulse types moved from
internal/pulsetojsoniopackage; velocity calculation no longer double-counts issues (xpo-b18d99) - Cosmetic consistency pass for
--jsonflags across all CLI commands — uniform flag descriptions and help text (xpo-66b8af)
Security
xpo boardno longer listens on all network interfaces. It binds127.0.0.1by default (--hostoverrides it and logs a warning for non-loopback addresses) and rejects requests whoseHostheader isn't a loopback name or the--hostvalue, blocking DNS rebinding. Previously anyone who could reach the port had unauthenticated read/write access, and in remote mode could use the user's bearer token through the proxy. The README documentsssh -Las the supported way to view a board on another machine. The Vite dev proxy targets127.0.0.1(xpo-809ad0)
Fixed
- Event timestamps in
issues.dbstrictly increase in file order again.mergestamped the DONE update about 100µs before its MERGE event, and cascading or link updates gave all their events one shared time, soxpo doctorreported out-of-order and tied timestamps after every merge. Storage now stamps each appended event, and each event in a rewritten web-UI tail, at least 1ns after the line before it, so this holds for every writer.mergeandupdatealso build their events in time order. This repo's 28 affected lines were re-timed once (xpo-e669d4) - Agent-written comments in the merge view's Conversation tab show the principal's avatar and "Claude Code on behalf of " instead of the raw agent identity;
xpo commentsand the TUI details pane show " (via )". Comments now carryon_behalf_ofin the HTTP API, remote transport, CLI--jsonand MCPshowoutput (xpo-a103c3) - Dropping an issue onto a Backlog row with no status group no longer sends
status: null(xpo-6e757d) - MergeView no longer re-renders the whole diff on every 3s uncommitted-diff poll or Refresh. Unchanged polls and refreshes no longer block the page (before: ~190–200ms per poll on a 1,000-line diff), and a change to one file re-renders only that file's card (xpo-5839aa)
- MergeView's All changes / Uncommitted toggle no longer shifts sideways by a couple of pixels when switching, because each label reserves its bold width (xpo-e64b2b)
- Switching Backlog tabs now loads that tab's saved filters instead of carrying over the previous tab's filters and overwriting the new tab's saved ones (xpo-0c0556)
- Clicking a label on the Labels page no longer briefly sets an invalid
#/backlogURL, and the applied label filter is saved like a manually chosen one (xpo-787e7b) - Issue activity timeline no longer logs a React missing-key warning for updates that change several fields (xpo-1657f1)
- MergeView diff panes no longer jump sideways when expanding or collapsing diffs; scrolling menus (Assignee, Cycle, filter submenus) now actually get the stable gutter their undefined
scrollbar-gutter-bothclass intended (xpo-c26f7b) - Backlog right-click context menu no longer stops working after the list was empty (at load, or after a search with no matches was cleared) (xpo-164038)
mergewithstrategy: ffno longer adds a separatexpo: merge <id>commit: the issue database and artifacts are amended into the fast-forwarded tip, and an explicitcommit_messagerewords it. Themergetool, the mcp-tools reference and skill step 9 now tell agents to omitstrategyunless the user asks for one. Re-runxpo initin other projects to refresh installed skill copies (xpo-c16e28)- Issue detail sidebar Cycle popover now uses the shared
CyclePicker(also used by the context menu), gaining a filter input,Cycle N (status)labels and keyboard navigation (xpo-f16b80) - Context Menu Cycle submenu now has a filter input and uses the shared
Menu/MenuItemstyling and keyboard navigation; cycles are labelledCycle N (status)so the checkmark only marks the selected cycle (xpo-70e66a) - Context Menu Assignee submenu now autofocuses its filter input and uses the shared
Menu/MenuItemstyling and keyboard navigation, matching Status, Priority and Labels (xpo-690930) - Every
xpocommand stalled ~5 s (and printed a strayESC ]11;?) under a pseudo-terminal that doesn't answer terminal queries: bubbletea v1's package init queried the background colour. Migrated the interactive prompts tocharm.land/huh/v2, which removes bubbletea v1 (xpo-d053b5) xpo initandxpo doctornow detect CLAUDE.md/AGENTS.md instruction blocks that differ from the current template even when the version is unchanged;initrefreshes them anddoctorreports "Instructions out of date" (fixed by--fix) (xpo-757732)- Backlog: ghost parents (faded parent rows shown above sub-issues in another status group) can be collapsed and expanded by click, ←/→ and Expand/Collapse all; the collapsed state is shared with the real parent row. While a search or filter is active, saved collapsed state is ignored so matches are never hidden, and collapses made during the search are temporary (xpo-0cbecc)
- Issue detail Activity avatars show the actor's gravatar or initials instead of the dashed placeholder; avatar and name share an "on behalf of …" tooltip when an agent acted for someone (xpo-53ed0e)
- Backlog: parent issues whose sub-issues are all hidden from the current view no longer show a non-clickable collapse chevron; the slot is left blank, like issues without sub-issues (xpo-e7c400)
- Agents no longer call
mergeon an uncommitted worktree. Step 9 of the generated xpo skill now requires a commit (it is a gate and an explicit step, with an optional changelog entry first), and the self-contradicting "do not commit" sentence is gone. Themergetool description says a committed worktree is required. Generated skill files end with anxpo:skillversion/hash marker:xpo doctorreports "Skill out of date" when a skill differs from the current template, even at the same CLI version, and reports local edits separately.xpo init/doctor --fixrefresh out-of-date skills and offer replace/keep/diff for edited ones instead of silently overwriting them (xpo-e88f1b) - The instance registry no longer treats every process as dead on Windows, which emptied the web UI instance list and pruned live peers on
Register. Liveness is now checked per platform:Signal(0)on Unix (withEPERMcounted as alive) andOpenProcess+GetExitCodeProcesson Windows.make lintalso runsGOOS=windows go vet ./...(xpo-60dc3c) update { links }now stores dependencies withsource_idset to the issue's ID and rejects self-links and duplicate (target, kind) pairs;links: []clears all links instead of failing with "no fields set". Existing rows with an emptysource_idare attributed to their owning issue on read, solinkbehaves correctly for them. Thelinktool, theadd/updatelinksschemas and the CLIlinkhelp now state that onlyblocked_bygatesstart(xpo-080fc4)- Concurrent writers (several MCP servers, the CLI, the web server) no longer lose or corrupt events in
issues.db: append, collapse and archive all hold an exclusive.xpo/events.lock, and each event line is written in a single call.xpo archivere-reads the log under the lock, keeps events written since the preview, and skips issues reopened since then, reporting them. Readers ignore a torn final line from an in-progress append (xpo-d6288f) xpo mergeno longer fails with a phantom "resolve the conflict" error when xpo runs from inside the issue's worktree: every merge git call now targets the hub checkout. MERGE/DONE events are recorded only after the code commit succeeds, so a failed merge (hook, conflict, nothing to commit) leaves the issue in its old status with nothing staged and never rewritesissues.db. Errors carry git's real output, and the conflict advice appears only for real conflicts (xpo-9fef67)- Web UI edit collapsing no longer merges edits across actors or past a newer event on the same issue, moves the merged event to the end so timestamps follow file order, keeps edited comments at their original time, and prunes no-op edits against running state; this repo's
issues.dbtimestamps were repaired once so time-ordered and file-ordered replay agree (xpo-e9a2eb) - Popover visibility in production builds: anchor ref not attached when
useLayoutEffectfires; deferred viaqueueMicrotask(xpo-b5a70c) - FilterMenu arrow-left key not registered for submenu entry (xpo-e16962)
- FilterMenu submenu options don't scroll into view on keyboard navigation (xpo-e16962)
- TopBar center slot now stays truly centered regardless of left/right content width changes (xpo-c078b7)
- MergeView "Files changed" badge now reflects the active scope — shows uncommitted file count when viewing uncommitted changes instead of always showing 0 (xpo-54a423)
- "All Changes" view falls back to uncommitted diffs when there are no commits yet, instead of showing an empty pane (xpo-54a423)
- Scope selector ("All changes" / "Uncommitted") promoted from file-tree sidebar to the tab bar for better visibility (xpo-54a423)
- Issues.db validation in
xpo doctorwith line-number reporting for invalid entries (xpo-76e2a1) comment --jsonnow accepts MCPCommentToolInputpayload format (xpo-eb0d61)worktree_setuphook output captured instead of leaking to stdout (xpo-7abb38)
Removed
-
BREAKING: branch mode is removed; worktrees are the only way to work on an issue (xpo-863802).
xpo startalways creates (or reuses) a worktree under.xpo/worktrees/, andxpo mergealways merges from the hub checkout and never runsgit checkout. Removed:xpo start --modeandxpo merge --no-wt, which now fail with "unknown flag".- The
modefield of the MCPstarttool andxpo start --json. Passing it is now rejected as an unknown field. - The
worktreesconfig key.worktrees: falseprints a warning and is ignored;worktrees: trueis a silent no-op, andxpo initno longer writes it. - The squash/ff
issues.dbunion-merge that only branch mode needed.
Migrating: before upgrading, merge or park any issue you started in branch mode, and check out the default branch in the primary checkout.
xpo mergerefuses to run while the hub is on another branch.xpo startrefuses an issue whose branch is checked out in the hub, and tells you to switch the hub back first. Branches without a worktree still merge normally from the hub. -
Unused
PendingEventsPanelcomponent (xpo-c26f7b) -
"Agent Identity" instructions telling agents to set themselves as assignee, from CLAUDE.md/AGENTS.md/GEMINI.md, the xpo skill and the generated templates (xpo-35fc16)
-
xpo init mcpandxpo init skillsubcommands — absorbed into unifiedxpo init(xpo-76e2a1) -
internal/inputsbackward-compat re-export shim — all CLI commands now usejsoniodirectly (xpo-aebfc5)