Add git-tree: local Git commit graph viewer - #8
Open
Microbiosis wants to merge 5 commits into
Open
Microbiosis wants to merge 5 commits into
Microbiosis wants to merge 5 commits into
Conversation
MiniApp that inspects a local Git repository's commit history: - Swim-lane commit graph (single SVG canvas, ported from zai-org/ZCode) - Branch / tag / working-tree status from parallel git calls - Inline (collapsible) and right-pane (always-on) commit detail - Filter state, theme, and refresh cadence persisted to <dataDir>/prefs.json - Portability fallback: on a fresh install, scans home dev directories and every mounted drive root on Windows to discover repos without any repos.json configuration Tested environment: MiniMax Code desktop 3.0.73.166 on Windows 10.0.26200 (x64). macOS and Linux not verified.
When the portability fallback kicks in (no `repos.json`, no walk-up match), the registry now recurses one level into first-level entries that look like a dev parent (`github`, `code`, `projects`, `workspace`, `src`, `dev`, `work`, `git`, `repos`, plus CJK variants `代码` / `项目` / `工程` / `源码`). This catches the common layout `D:\Github\<repo>\` where the user's projects live two levels deep under the drive root, which the previous single-level scan missed. On the maintainer's Windows machine this lifts the discovery count from 14 to 27 (the 13 new entries are all under `D:\Github\`). Cost is bounded: one extra `readdir` per matching dev-parent entry, and we only recurse when the immediate child does not already carry `.git`. The dev-parent name set is intentionally narrow (no fuzzy heuristics like `play` / `stuff`) so the recursion cannot walk into arbitrary folders. - Update `miniapp/node/server.mjs`: add `DEV_PARENT_HINTS` set and `isLikelyDevParentName`, recurse one level inside `buildRegistry`'s scan loop when the entry matches. - Update `README.md` and `README.zh-CN.md` Data sources section to describe the recursive step.
The previous `slice(0, 60)` was applied to *every* scan-base entry before the `.git` check, which silently dropped legitimate repos that happened to land past index 60 in the alphabetical listing. Concrete failure on the maintainer's Windows host: `D:\` has ~110 non-skipped entries; `D:\synthetic-git-repo` sat at index 81 and never reached the `.git` stat. After this change, all 27 repos are discovered (arcreel-connect, synthetic-git-repo, plus the 13 `D:\Github\<repo>\` projects and 12 on `E:\`). - Direct `.git` check is now uncapped. One stat per non-skipped entry on a local SSD is cheap enough that the cap was never justified. Stat calls run in parallel via `Promise.all` to keep wall time flat. - The dev-parent recursion (which costs an extra `readdir` per matching entry) keeps the 60-entry cap to bound worst-case fan-out. - Set `repos[0].isDefault = true` and `defaultRepo = repos[0].path` in the fallback path so the client dropdown opens with a selection instead of forcing the user to pick one manually when no walk-up produced a preferred repo. Verified end-to-end against `D:\synthetic-git-repo`: 5 commits, 2 lanes, merge commit correctly identified at depth 1 with parent edges on both lanes.
The port had two deviations from upstream's `packages/ui/src/git-graph/layout.ts`: - `curveOffset = Math.max(8, Math.abs(toY - fromY) * 0.5)` → `Math.max(14, Math.abs(toY - fromY) * 0.38)`. The minimum offset of 14 and the 0.38 factor produce gentler Bezier curves at short row distances and match what ZCode renders. The previous values were tighter curves that diverged from the upstream visual. - File header comment claimed the port emits per-row primitives (stale from an earlier draft that the client has since replaced with a single SVG canvas). Updated to describe the actual divergence (default pixel sizes reduced for the Mini App surface) and to claim byte-equivalence for the algorithm and curve constants. Also dropped two unused fields (`laneIndices`, `laneByHash`) that were computed by `createGitGraphLayoutModel` but never read by any consumer. The returned shape now matches upstream exactly. Verified end-to-end against a synthetic merge graph (HEAD at index 0, oldest at the tail — the order `git log --topo-order` produces). Output paths include both straight-line edges on the same lane and Bezier curves on cross-lane connections, with `curveOffset = max(14, |dy| * 0.38)` exactly matching the upstream formula.
Every fix below was reproduced against a running server and a real browser before being written; none were visible to a syntax or import smoke test. 1. Paginated graph layout was per-page, not per-window. buildGraph() is stateful across rows (lane colour reuse, merge-path detection, lockedFirst), so laying out page N in isolation disagreed with the pages around it: a branch opened on page 1 lost its lane on page 2, and merges stopped resolving once scrolled past the first page. Lay out the whole accumulated window instead - git log already fetched those rows, so this costs no extra call. Only the current page commits go over the wire. 2. The client concatenated the graph geometry arrays on each page. They are absolute-indexed and describe the whole accumulated list, so concat duplicated every page rows and left the canvas sized to the first page only. Replace them, then re-run applyGraphSize() + renderCanvas() so rows past page 1 get lanes and dots. 3. A cleared filter could not be persisted. sanitizePrefs() dropped empty strings, so an empty ref - how the client says the filter was cleared - was silently discarded and the merge resurrected the old value on reload. Accept the empty string as a meaningful value; for the ref key it normalises to the default all scope. 4. An oversized prefs body reset the connection instead of answering. req.destroy() tore the socket down before sendJson could write the response, so the client saw a bare reset rather than the designed error. Stop accumulating, let the stream drain, and reject with a sentinel that becomes a real 413 body_too_large response. 5. Two path/config normalisation bugs. repos.json written by a Windows editor carries a UTF-8 BOM that JSON.parse rejects, which made an explicitly configured allowlist fail silently and fall back to discovery - parse it with the BOM stripped. And buildRegistry() pushed raw D:/foo paths while defaultRepo handed the client resolve()d ones, so the repo dropdown could not match its own option list and rendered as nothing selected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
git-tree— a MiniApp that inspects a local Git repository's commit history.Plugin directory:
plugins/microbiosis/git-tree/Plugin ID (
namein.minimax-plugin/plugin.json):git-treeAuthor (plugin.json):
redmingweiThis is a re-submission of the previously closed PR #7. The five bugs below
were found by running the plugin for real — live server plus a real browser
driven by Puppeteer — and are fixed in this branch. All five were invisible to
syntax/import smoke tests; each one was reproduced first, then fixed, then
re-verified through the same interfaces.
Bugs fixed since #7
buildGraph()isstateful across rows (lane colour reuse, merge-path detection,
lockedFirst), so laying out page N in isolation disagreed with the pagesaround it: a branch opened on page 1 lost its lane on page 2, and merges
stopped resolving once scrolled past the first page. Now the whole
accumulated window is laid out.
git log -n offset+limit+1already fetchedthose rows, so this costs no extra git call; only the current page's commits
go over the wire.
and describe the whole accumulated list, so
concatduplicated every page'srows and left the canvas sized to the first page only. They now replace,
followed by
applyGraphSize()+renderCanvas()so rows past page 1 getlanes and dots.
sanitizePrefs()dropped emptystrings, so an empty
ref— how the client says the filter was cleared —was silently discarded and the merge resurrected the old value on reload.
req.destroy()tore the socket down beforesendJsoncould write theresponse, so the client saw a bare connection reset rather than the designed
error body. Now the stream drains and the caller returns a real
413 body_too_large.repos.jsonwritten by a Windows editorcarries a UTF-8 BOM that
JSON.parserejects, which made an explicitlyconfigured allowlist fail silently and fall back to discovery. And
buildRegistry()pushed rawD:/foopaths whiledefaultRepohanded theclient
resolve()d ones, so the repo dropdown could not match its ownoption list and rendered as "nothing selected".
Features
miniapp/node/git-graph.mjsis a JavaScript port ofzai-org/ZCode'slayoutAlgorithm.ts/layout.ts, which are Apache-2.0. Attribution is preserved inLICENSEandREADME.md.gitcommands, cached 30 s per repo).git log <ref> --topo-order+ optional--grep/--author; up to 400 rows per call; result server-validated, client renders one SVG.theme,repo,ref,q,author,interval) persisted to<dataDir>/prefs.json; merged writes via/api/prefs(POST never wipes siblings), 400 ms client-side debounce.api()caps at 35 s withAbortController. API responses ≥ 256 bytes are gzipped whenAccept-Encoding: gzipis sent.repos.jsonempty installs: when the user has not authoredrepos.jsonand the plugin is not installed inside any git tree, the registry also scans~/Code,~/Projects,~/repos,~/workspace,~/src,~/source,~/dev,~/work,~/Documents,~/git(case-insensitive on Windows/macOS), plus on Windows every mounted drive root (A:\…Z:\). Windows system hives (Program Files,Windows,Users,ProgramData,$Recycle.Bin, …) and macOS resource dirs (Library,Applications,System) are filtered so widening to drive roots cannot recurse into%ProgramFiles%. The fallback only activates when no other discovery source is present, so users with an explicitrepos.jsonare unaffected.File layout
What was tested
3.0.73.166, Windows10.0.26200(x64).docs/preview.jpgis generated from this synthetic data.D:\andE:\without anyrepos.jsonconfiguration.GET /git-treereturns 200, and the fixed endpoints return the corrected payloads.Not verified: macOS and Linux. The portability fallback is implemented for both platforms (Unix home parent + common dev directories; Windows drive roots), but only the Windows path has live confirmation.
Submission checklist
plugins/<github-username>/<plugin-id>/.git-treecollision)..minimax-plugin/,package.json,miniapp/, and runtime assets preserved.README.md(with mutual link toREADME.zh-CN.md); documented install path, verified client version + OS, configuration, and required file / network access.LICENSE(Apache-2.0) with explicit attribution tozai-org/ZCodefor the ported layout algorithm.node_modules/, no secrets, no real session data. Screenshot uses synthetic data.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.