Add git-tree: local Git commit graph viewer - #8
Conversation
MyPrototypeWhat
left a comment
There was a problem hiding this comment.
Thanks for the resubmission — the five e2e fixes all check out against the code, and the package structure, ZCode attribution, and synthetic preview all look good.
Since this is a community repo we mostly gate on the submission rules and on what users are told, not on internal code quality. Three things to sort out before merge:
1. Author attribution is inconsistent
The directory is plugins/microbiosis/ and the PR is from Microbiosis, but the root README.md / README.zh-CN.md rows link to github.com/redmingwei, .minimax-plugin/plugin.json has "author": "redmingwei", and the plugin README says Author: Microbiosis (unlinked). Per CONTRIBUTING the author column should point at the GitHub username that owns the directory — please pick one and use it everywhere. The other plugins use Author: [name](https://github.com/name) in their README.
2. README needs to cover file access and network requests
CONTRIBUTING asks each plugin README to explain "file access, and network requests". Network isn't mentioned at all — a one-liner like "makes no network requests" is enough if that's the case. On file access, the fallback scan on macOS/Linux also walks the parent of $HOME (server.mjs:252-257, i.e. /Users or /home, so other users' home directories), and the Windows A:\–Z:\ scan will touch mapped network drives. Users should be able to see that scope before installing.
3. "Users with an explicit repos.json are unaffected" isn't true as written
server.mjs:310 only checks scanBases.size === 0, and entries from config.repos never go into scanBases, so a repos.json with only repos (no scanRoots) still triggers the full home/drive scan. Either also gate the fallback on config.repos being empty, or adjust the README/PR wording — whichever you prefer, as long as the doc matches the behaviour.
Non-blocking, in case you want to tidy up while you're in there: the README's "refs chip with is-head/is-tag" never renders (client reads commit.refs at index.html:1209, server only sends tags); "first 200 commits (configurable via repos.json)" is actually a hard-coded PAGE_LIMIT; and the 35 s client timeout is 60 s for graph calls (LONG_TIMEOUT_MS).
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.
The repo contract added by upstream now expects these headings in every plugin README. Fill them in with the real facts: local git reads only, no network egress, and the exact tested desktop version + Windows build.
1a8385c to
3cc348b
Compare
- Attribute the plugin to Microbiosis (gh api users/redmingwei returns 404: redmingwei is the GitHub profile display name, not a login, so the README link github.com/redmingwei was dead). Unified across both root indexes, plugin.json and both plugin READMEs. - Drop the unused refs chip row: the server never sends a per-commit refs field, so refsHtml() always returned an empty string and .row-refs / .ref-chip.is-head were dead code. Branches/tags still render in the detail panels via .ref-chip.is-tag. - Gate the whole-machine scan fallback on config.repos.length === 0 as well, so users who declared repos explicitly never trigger a drive-root scan. - Correct docs: 200 commits is a fixed client page size, not a repos.json setting; client timeout is 35s default and 60s for graph requests; expand Data & access into read / discovery / write / network sections.
3bb5e69 to
ba9457f
Compare
|
Thanks — all six points are addressed in Blocking
Non-blocking
|
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.