Skip to content

fix(GenesisKit, markdown, git): the git sidebar's Changes list shows moves as renames, the newest change first, and scrolls 3 to 5 times cheaper - #572

Merged
genesiscz merged 21 commits into
masterfrom
feat/2026-10-25-repo-panel
Oct 26, 2026
Merged

genesiscz merged 21 commits into
masterfrom
feat/2026-10-25-repo-panel

Conversation

@genesiscz

@genesiscz genesiscz commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #581: it changes GitFileDiffModel's failure states and GitRepoPanel's keyed rows, which #581 adds (ee708a5); #581 sits on #563.

What Martin reported (2026-10-25 19:06) and what changed

  1. A deleted file said "This file is not in a git repository". git was started in the deleted file's folder, which no longer exists. GitQueries.root(of:) now starts in the nearest existing folder, so the deleted diff opens (02b5a96).
  2. The Repository tab then said the same. The sidebar keeps the last repository when the open file is outside one (02b5a96).
  3. "Last update" topped out at a note 5 h old. Two causes. The status refreshed only on .git events, so edits by other programs never reached it; a working-tree watcher now refreshes it at most every 3 s (c6b4b56). Untracked files had their own section below the tracked edits; Last update is now one newest-first list (5787bf9).
  4. 650 "deletions" were a folder moved outside git. Exact-content rename pairing (size filter, then hash only the matches; empty files never pair) shared by the sidebar, the Changes pane, the hub's working-tree diff and tools git changes, with one fixture both languages read (02b5a96, 6a29a2b). On the vault: 142 renames and 508 real deletions.
  5. One engine. The pairing lives in GenesisKit (GitWorktreeRenames) and in src/utils/git/worktree-renames.ts, with contract-worktree-renames.json as the shared contract.
  6. The panel lagged. See the numbers below.

Added during the work, at Martin's request: the git sidebar's tabs are This file / Changes / Commits (4866f98); "No unstaged changes in this file." no longer flashes on every refresh (c85a5ac); the commit sheet's keyboard ring sits on its row (38199e7); the Changes sections fold (d2c252e) with the outline keys gt-native-ux asked for (f7c6aed). The rename hash cache keys on the ctime too (793cd77).

Changed from Martin's 2026-10-13 call: the change lists no longer page with "Show more". The table builds only the rows on screen. .claude/rules/markdown-viewer.md says so.

Numbers

Bench MarkdownRepoPanelBenchTests (MARKDOWN_REPO_BENCH=<repo>), the notes vault with 5,650 status entries, debug build, main-thread CPU medians in ms. "after2" is 4 interleaved rounds; "probe3" is one run of the later commits (an observation).

What the reader does Before After Source
Scroll one 45 pt step 9.4 5.8 after2
Scroll with a 600-row folder open 12.2 4.0 after2
Unrelated publish of the documents model 22.9 8.6 after2
Tab switch 142 74 probe3
Git event, status unchanged 134 28 to 43 probe3, after2
Change the sort order 68 33 probe3
Open a folder 56 22 after2
Close a folder 63 57 after2
Filter one key 56 59 probe3 (no gain)
Switch the sidebar to Changes 289 252 probe3

The installed release viewer (Genesis Markdown, the vault, git sidebar on Changes, load 18 to 29 in both), 360 wheel events from tools control scroll … --repeat 4 --alternate, CPU from ps:

Before After
CPU for 360 scroll events 6.86 s (one round) 2.02, 1.87, 1.24 s (three rounds)
Busy main-thread samples while scrolling 5,533 of 7,421 (75%) 529 of 2,628 (20%)
Top of the busy time 4,609 samples in MarkdownRootHostingView.layout no frame above 35 samples

Not better: filter typing (a filter opens every folder, and with no pages it shows every match).

How the speed was found

  • The rows moved from a LazyVStack inside the window's SwiftUI root to an NSTableView of hosted cells over the sidebar (GitRepoTable, the file tree's pattern), with one row height.
  • The first table build made sort, filter and fold slower (68 → 195 ms). A sample showed reloadData re-running the key-view loop for every row (1,159 samples) and moving every host into the window (892). Rows now refill in place; only changed cells rebuild.
  • The sidebar and panel are Equatable and stopped observing the whole documents model; no .focusable() over the rows.
  • A status read that finds the same status wakes no follower (generation on change, reads for the dates); status lookups by path are O(1).
  • Durable profiling: git.worktreeRenames, git.repoStatus spans, git.row / git.repoPanel body probes, and git.panel.* main-busy windows in perf.log.

The lessons are in the swift, ux and performance skills (631b6ec, 65804ba), reviewed and cleared by gt-native-ux.

Moved out of this PR

The swift/ux/performance skill lessons are here except the eight performance/references/patterns.md lines: that file is added by gt-native-ux's docs branch docs/2026-10-25-ux-swift-audit-skills, which carries them (0fcd26d).

Tests

swift test --filter 'GitRepoPanelKeyboardTests|GitReposTests|GitFileDiffTests|GitWorktreeRenamesTests': 40 tests, 0 failures, 1 skipped (the env-gated real-repository test). bun test src/utils/git/worktree-renames.test.ts for the TS twin.

Not yet checked by hand on the installed key window: key repeat, the keyed row scrolling into view, reveal under Reduce Motion, VoiceOver reading.

@eve-bot-lovinka

eve-bot-lovinka Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🐉 eve review — ⏭️ superseded

→ run

  • Queued 05:09:01Z
  • Reading diff — 300 files
  • Building repo map
  • Analyzing (find → verify)
  • Posting review
  • Done 05:12:34Z (3m 33s)

superseded — head moved to f010b18 during prep; the queued review covers it

next attempt 05:10:45Z

run wrun_01M5V9SEHVW8SX6YC7RC49PVRB

Previous runs (10)
run head outcome findings took
11 a935db9 ⏳ refused — 3m 9s
11 a935db9 🔁 deduped — —
10 359497d ✅ APPROVE 2 10m 6s
9 5a5e7a2 ✅ APPROVE 1 4m 51s
8 d4b94d1 ✅ APPROVE 2 15m 37s
8 d4b94d1 🔁 deduped (run wrun_01M owns it) — —
7 a2b2b0e ✅ APPROVE 3 20m 8s
6 a2b2b0e ⏳ refused — 3m 44s
5 efcd6de ✅ APPROVE 3 18m 18s
5 efcd6de 🔁 deduped (run wrun_01M owns it) — —

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 14 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available. Your 51 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between c4513cc and f010b18.

Files selected for processing: 34
  • .claude/rules/markdown-viewer.md
  • .claude/skills/performance/references/caching-and-freshness.md
  • .claude/skills/swift/references/gotchas.md
  • .claude/skills/swift/references/performance.md
  • .claude/skills/ux/references/checklist.md
  • .claude/skills/ux/references/findings.md
  • src/git/lib/changes-read.ts
  • src/git/lib/changes.ts
  • src/macos/GenesisKit/Package.swift
  • src/macos/GenesisKit/README.md
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Model/GitState.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitFileDiffModel.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitQueries.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitRepos.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitWorkingTreeSource.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitWorktreeRenames.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Views/GitHistoryViews.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Views/GitRepoPanel.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Diff/Views/GitRepoTable.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Markdown/Views/MarkdownChangesPane.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Markdown/Views/MarkdownGitViews.swift
  • src/macos/GenesisKit/Sources/GenesisKit/Markdown/Window/MarkdownDocumentsView.swift
  • src/macos/GenesisKit/Sources/GenesisKitFixtures/Git/contract-worktree-renames.json
  • src/macos/GenesisKit/Sources/GenesisKitFixtures/KitFixture.swift
  • src/macos/GenesisKit/Tests/GenesisKitTests/Diff/GitFileDiffTests.swift
  • src/macos/GenesisKit/Tests/GenesisKitTests/Diff/GitHistoryTests.swift
  • src/macos/GenesisKit/Tests/GenesisKitTests/Diff/GitRepoPanelKeyboardTests.swift
  • src/macos/GenesisKit/Tests/GenesisKitTests/Diff/GitReposTests.swift
  • src/macos/GenesisKit/Tests/GenesisKitTests/Diff/GitWorktreeRenamesTests.swift
  • src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownRepoPanelBenchTests.swift
  • src/macos/lib/kit-fixtures.ts
  • src/utils/git/index.ts
  • src/utils/git/worktree-renames.test.ts
  • src/utils/git/worktree-renames.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — ✅ Approved

fe82f85 · 0 actionable findings · reviewed by codex/gpt-6.1-sol + codex/gpt-6-luna · view run ↗

Blast radius

  • Keyed (+) → 12 dependent(s) outside this PR: src/macos/GenesisKit/Sources/GenesisKitCharts/TreemapView.swift, src/macos/GenesisKit/Sources/GenesisKitCharts/HealthBar.swift, src/macos/GenesisKit/Sources/GenesisKit/Sessions/Transcript/TranscriptBus.swift, +9 more
  • readKitFixtureJson (Δsig) → 9 dependent(s) outside this PR: src/macos/commands/clones/reclaim.test.ts, src/macos/commands/clones/optimize.test.ts, src/macos/lib/kit-fixtures.test.ts, +3 more
  • Recent (+) → 9 dependent(s) outside this PR: src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownRecentsTests.swift, src/macos/GenesisKit/Sources/GenesisKit/Sessions/Transcript/TranscriptScrollAnchor.swift, src/macos/GenesisKit/Sources/GenesisKit/Flow/FlowWidget.swift, +6 more
  • controls (~) → 8 dependent(s) outside this PR: native/ax-tool/Sources/main.swift, native/ax-tool/Sources/IsolatedCapture.swift, native/ax-tool/Sources/commands/Harness.swift, +5 more
  • runGit (+) → 8 dependent(s) outside this PR: scripts/ci/pre-commit-hook.test.ts, plugins/genesis-tools/skills/git/evals/fixtures/build.ts, src/scripts/commands/git.ts, +5 more
  • Key (~) → 7 dependent(s) outside this PR: native/ax-tool/Sources/main.swift, native/ax-tool/Sources/Record.swift, native/ax-tool/Sources/commands/Harness.swift, +4 more
  • pair (+) → 7 dependent(s) outside this PR: native/ax-tool/Sources/SnapshotWorkflow.swift, native/ax-tool/SnapshotSupport/VisualCapture.swift, native/ax-tool/SnapshotSupport/OcrReuse.swift, +4 more
  • update (~) → 7 dependent(s) outside this PR: native/ax-tool/Sources/Record.swift, native/ax-tool/Sources/IsolatedCapture.swift, native/ax-tool/Sources/utils/Output.swift, +4 more
  • Contract (+) → 6 dependent(s) outside this PR: src/macos/commands/clones/optimize.ts, src/benchmark/lib/baseline.ts, src/utils/fs/disk-usage.test.ts, +3 more
  • Identity (+) → 6 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/IsolatedComposition.swift, src/macos/GenesisKit/Sources/GenesisKit/Settings/KitSettingsBackend.swift, src/macos/GenesisKit/Sources/GenesisKit/Clicky/ClickyPacks.swift, +3 more
  • +28 more changed symbol(s) with outside dependents

ℹ️ Claims vs diff — 8 claims could not be checked from the diff (c1, c2, c3, c4, c5, c6, c7, c8).

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🟡 Review comments

f7c6aed · 5 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟡 Medium 4
🔵 Low 1

Blast radius

  • Keyed (+) → 12 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Host/Notify.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubSessionInsights.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubTimeline.swift, +9 more
  • controls (~) → 9 dependent(s) outside this PR: native/ax-tool/Sources/commands/Harness.swift, native/ax-tool/SnapshotSupport/HierarchySource.swift, src/macos/GenesisTools/Package.swift, +6 more
  • Key (~) → 9 dependent(s) outside this PR: native/ax-tool/Sources/commands/Annotate.swift, native/ax-tool/Sources/commands/Harness.swift, native/ax-tool/Sources/main.swift, +6 more
  • readKitFixtureJson (Δsig) → 9 dependent(s) outside this PR: src/du/lib/index/index.test.ts, src/macos/commands/clones/reclaim.test.ts, src/macos/commands/clones/optimize.test.ts, +3 more
  • Recent (+) → 9 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubNotify.swift, src/macos/GenesisTools/Sources/Parts/Features/Settings/DaemonSettings.swift, src/macos/GenesisKit/Sources/GenesisKit/Markdown/Document/VaultIndex.swift, +6 more
  • pair (+) → 8 dependent(s) outside this PR: native/ax-tool/Sources/SnapshotWorkflow.swift, native/ax-tool/SnapshotSupport/FrameSampler.swift, native/ax-tool/SnapshotSupport/ContextMenu.swift, +5 more
  • runGit (+) → 8 dependent(s) outside this PR: scripts/ci/pre-commit-hook.test.ts, src/utils/agent-sessions/testing/fixture-world.ts, src/utils/git/test-repo.ts, +5 more
  • header (+) → 7 dependent(s) outside this PR: native/ax-tool/Sources/commands/Annotate.swift, src/macos/GenesisTools/Tests/ReviewScopeChipShots.swift, src/macos/GenesisTools/Tests/HubNavigationTests.swift, +4 more
  • update (~) → 7 dependent(s) outside this PR: native/ax-tool/Sources/Record.swift, native/ax-tool/Sources/IsolatedCapture.swift, native/ax-tool/Sources/utils/Output.swift, +4 more
  • apply (+) → 6 dependent(s) outside this PR: native/ax-tool/Sources/SnapshotWorkflow.swift, native/ax-tool/SnapshotSupport/ContextMenu.swift, native/ax-tool/SnapshotSupport/VisualCapture.swift, +3 more
  • +26 more changed symbol(s) with outside dependents

ℹ️ Claims vs diff — 7 claims could not be checked from the diff (c1, c2, c3, c4, c5, c6, c7).

Comment thread src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitQueries.swift Outdated
@genesiscz
genesiscz force-pushed the feat/2026-10-25-markdown-viewer-ux branch from e1c71e7 to bc86241 Compare October 26, 2026 01:26
genesiscz added a commit that referenced this pull request Oct 26, 2026
…section, rename identity uses every candidate, date invalidation waits for its result, and staged files keep their index paths
@genesiscz
genesiscz force-pushed the feat/2026-10-25-repo-panel branch from f7c6aed to 47dae8a Compare October 26, 2026 02:14
@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 03:14 from GPT-6.1 Sol judged by Claude Opus 5.5 (fable-judge)

Round 1. 5 open threads (eve 5, CodeRabbit 0). All 5 fixed, 0 rejected, 0 already done. Full self-review of the 32 changed files added 2 fixed findings. Fixed in 802af50; the lead ran the tests and committed.

Reveal unfolds the file's containing section (eve-bot-lovinka)

  • Context: The reveal lookup includes files in folded sections, but the table omits their rows. Opening only the folder left reveal without a row to scroll to.
  • Commit(s): 802af5005
  • Verdict: accepted
  • Code before:
    if let folder = place.folder { openFolders.insert(folder) }
  • Code after:
    setFolded(place.section(order: derived.settled?.order ?? order), false)
    if let folder = place.folder { openFolders.insert(folder) }
  • How was this fixed: Place resolves its section for the settled layout. Reveal unfolds it before opening the folder and scheduling the scroll. Added GitRepoPanelKeyboardTests.testRevealUnfoldsTheContainingSectionInEveryLayout, covering staged, changed, loose and grouped untracked files in all four orders while retaining other folds. Verification:.
  • Confidence: 95%

Resolve file rename identity from the complete candidate set (eve-bot-lovinka)

  • Context: Pairing each requested file separately reused deletions and assigned duplicate blobs different identities from the repository panel.
  • Commit(s): 802af5005
  • Verdict: accepted
  • Code before:
    return GitWorktreeRenames.find(in: root, deleted: deleted, untracked: [path], revision: "", provider: provider).first?.old
  • Code after:
    let entries = GitWorktreeRenames.paired(Self.parseStatus(output).entries, in: root, provider: provider)
    return entries.first { $0.path == path && $0.unstaged == .renamed }?.oldPath
  • How was this fixed: movedFrom requests all untracked entries and uses the same complete pairing as repoStatus. Added GitWorktreeRenamesTests.testFileStateUsesTheRepositorysOneUsePairing with two deleted duplicate blobs, their same-name destinations and a surplus copy. It checks agreement with repoStatus and leaves the surplus copy untracked. Verification:.
  • Confidence: 96%

Keep date invalidation pending until its result is applied (eve-bot-lovinka)

  • Context: Scheduling a date read previously marked its version current. A filter or sort update could discard that read and derive from stale dates.
  • Commit(s): 802af5005
  • Verdict: accepted
  • Code before:
    let reread = rootChanged || statusChanged || reads != self.reads
    self.status = status
    self.reads = reads
  • Code after:
    let dateVersion = DateVersion(root: inputs.root, status: status, reads: reads)
    let reread = rootChanged || dateVersion != appliedDateVersion
    // apply, after its ticket guard:
    appliedDateVersion = dateVersion
  • How was this fixed: The applied date version records root, status and read counter only after the current ticket wins. Derivation compares status and inputs with the applied snapshot. Root changes always reread after clearing the lists. Added GitFileDiffTests.testPendingDatesSurviveAnInputChange and testReturningToAnAppliedRootStillReadsDatesAfterClearingItsLists. Existing equal-read and stale-root tests remain. Verification:.
  • Confidence: 93%

Keep staged files at their index paths when moved outside git (eve-bot-lovinka)

  • Context: Rewriting a staged entry to an untracked destination hid its index path and overwrote a staged rename's original path. Staged diffs then addressed missing index entries. TypeScript shared this rewrite.
  • Commit(s): 802af5005
  • Verdict: accepted
  • Code before:
    let news = Set(pairs.map(\.new))
  • Code after:
    let eligible = Set(entries.filter { $0.staged == nil && $0.unstaged == .deleted }.map(\.path))
    let accepted = pairs.filter { eligible.contains($0.old) }
    let news = Set(accepted.map(\.new))
  • How was this fixed: Both status pairing implementations exclude sources with staged changes. Their apply functions independently enforce that rule before removing destinations. These cases retain the source deletion and untracked destination, preserving staged content and rename identity. Added GitWorktreeRenamesTests.testStagedChangesKeepTheirIndexPathsWhenMovedAgain and the TypeScript regression 'preserves staged modifications, additions and rename identities after another move', including direct apply calls. Verification:.
  • Confidence: 95%

Update the public repository panel example (eve-bot-lovinka)

  • Context: The README passed removed initializer arguments and described commits as part of GitRepoPanel.
  • Commit(s): 802af5005
  • Verdict: accepted
  • Code before:
    GitRepoPanel(repo: repo, log: log, onOpenFile: { entry, staged in … }, onOpenCommit: { commit in … })
  • Code after:
    GitRepoPanel(repo: repo, onOpenFile: { entry, staged in … })
    GitCommitList(commits: log.commits, onOpen: { commit in … })
  • How was this fixed: Updated the example and description to the current initializer and a separate GitCommitList. README.md is outside the original diff and is necessary to repair the public API documentation. Compared the example with the current initializers by source inspection. Verification:.
  • Confidence: 99%

Keep quote-prefixed paths literal in the hash batch (self-review, no thread)

  • Context: git hash-object --stdin-paths interprets a leading double quote as a C-quoted pathname. A legitimate filename beginning with an unmatched quote could fail the complete batch and suppress unrelated matches. Both implementations sent raw paths.
  • Commit(s): 802af5005
  • Verdict: accepted
  • Code before:
    candidates.map((path) => `${path}\n`).join("")
  • Code after:
    candidates.map((path) => `./${path}\n`).join("")
  • How was this fixed: Both implementations prefix hash input paths with ./, preserving the file while preventing quote-protocol interpretation. Added GitWorktreeRenamesTests.testAQuotedDestinationDoesNotBreakTheHashBatch and the TypeScript test 'hashes a destination beginning with a quote without breaking other pairs', including an ordinary pair in the same batch. Verification:.
  • Confidence: 94%

Follow shared environment and diagnostic contracts (self-review, no thread)

  • Context: The new helper read process.env directly despite code.md, silently swallowed stat errors despite tools.md, and piped git stderr without consuming or retaining it. Failed queries appeared as no rename matches.
  • Commit(s): 802af5005
  • Verdict: accepted
  • Code before:
    env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
    const [stdout, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
    return code === 0 ? stdout : null;
  • Code after:
    env: { ...env.getProcessEnv(), GIT_OPTIONAL_LOCKS: "0" },
    const [stdout, stderr, code] = await Promise.all([
        new Response(proc.stdout).text(),
        new Response(proc.stderr).text(),
        proc.exited,
    ]);
  • How was this fixed: Used env.getProcessEnv for child inheritance, consumed both output streams concurrently, logged failed git queries and unavailable stat candidates at debug level, and gave the three-parameter helper the documented object argument form. Existing rename integration coverage exercises its normal path. No new test file was added. Verification:.
  • Confidence: 98%

Threads: t1 accepted · t2 accepted · t3 accepted · t4 accepted · t5 accepted

genesiscz added a commit that referenced this pull request Oct 26, 2026
…section, rename identity uses every candidate, date invalidation waits for its result, and staged files keep their index paths
@genesiscz
genesiscz force-pushed the feat/2026-10-25-repo-panel branch from 47dae8a to efcd6de Compare October 26, 2026 02:17
@genesiscz
genesiscz force-pushed the feat/2026-10-25-markdown-viewer-ux branch from 241716b to d5ea372 Compare October 26, 2026 02:32

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🟡 Review comments

efcd6de · 3 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟡 Medium 2
🔵 Low 1

ℹ️ Checks (advisory — CI is authoritative) — typecheck timed out (tsconfig.json): out of memory, then its 6144 MiB retry ran out of time

Blast radius

  • Keyed (+) → 12 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubTimeline.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubWindow.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubWorktrees.swift, +9 more
  • readKitFixtureJson (Δsig) → 9 dependent(s) outside this PR: src/doctor/tests/reclaim-analyzers.test.ts, src/doctor/tests/act.test.ts, src/macos/lib/kit-fixtures.test.ts, +3 more
  • Recent (+) → 9 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubNotify.swift, src/macos/GenesisTools/Sources/Parts/Features/Settings/DaemonSettings.swift, src/macos/GenesisKit/Sources/GenesisKit/Flow/FlowWidget.swift, +6 more
  • runGit (+) → 8 dependent(s) outside this PR: src/utils/agent-sessions/testing/fixture-world.ts, src/utils/git/origins/pr-fetch.test.ts, src/utils/git/test-repo.ts, +5 more
  • controls (~) → 7 dependent(s) outside this PR: src/macos/GenesisTools/Package.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubHandoffComposer.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubBench.swift, +4 more
  • Contract (+) → 6 dependent(s) outside this PR: src/benchmark/lib/baseline.ts, src/utils/fs/disk-usage.test.ts, src/utils/fs/disk-usage.ts, +3 more
  • pair (+) → 6 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubWindow.swift, src/macos/GenesisTools/Sources/Parts/Hub/Review/ReviewWindow.swift, src/macos/GenesisTools/Sources/Parts/Hub/Review/ReviewSessionState.swift, +3 more
  • section (+) → 6 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubPRs.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubPanelFind.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubLaunch.swift, +3 more
  • Identity (+) → 5 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubInbox.swift, src/macos/GenesisTools/Sources/Host/VaultRecipient.swift, src/macos/GenesisTools/Sources/Host/VaultUnlock.swift, +2 more
  • Key (~) → 5 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubWindow.swift, src/macos/GenesisTools/Tests/ReviewSessionStateTests.swift, src/macos/GenesisKit/Sources/GenesisKitPosture/Pose/PoseSample.swift, +2 more
  • +26 more changed symbol(s) with outside dependents

ℹ️ Claims vs diff — 5 claims could not be checked from the diff (c2, c3, c4, c5, c8).

Comment thread src/macos/GenesisKit/Sources/GenesisKit/Markdown/Views/MarkdownGitViews.swift Outdated
Comment thread src/utils/git/worktree-renames.ts Outdated
genesiscz added a commit that referenced this pull request Oct 26, 2026
…section, rename identity uses every candidate, date invalidation waits for its result, and staged files keep their index paths
@genesiscz
genesiscz force-pushed the feat/2026-10-25-repo-panel branch from efcd6de to a2b2b0e Compare October 26, 2026 02:46
@genesiscz
genesiscz force-pushed the feat/2026-10-25-markdown-viewer-ux branch from d5ea372 to 407c989 Compare October 26, 2026 03:05
genesiscz added a commit that referenced this pull request Oct 26, 2026
…section, rename identity uses every candidate, date invalidation waits for its result, and staged files keep their index paths
genesiscz added a commit that referenced this pull request Oct 26, 2026
…alize line endings and clean filters before pairing, and the file history follows the review
@genesiscz
genesiscz force-pushed the feat/2026-10-25-repo-panel branch from a2b2b0e to d4b94d1 Compare October 26, 2026 03:07
@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 04:07 from GPT-6.1 Sol judged by Claude Opus 5.5 (fable-judge)

Round 2. 3 unresolved threads (eve 3, CodeRabbit 0): 3 fixed, 0 rejected. Two threads describe the same missing repository-history failure notice. Fixed in d4b94d1; the lead ran the tests and committed.

Show repository history failures and retry in the Commits tab (eve-bot-lovinka)

  • Context: GitHistoryModel marks a failed read loaded while retaining its prior commits. The Commits tab ignored failure, so an initial error appeared as No commits yet and a failed refresh silently displayed stale commits.
  • Commit(s): d4b94d17a
  • Verdict: accepted
  • Code before:
    if repoLog.loaded && repoLog.commits.isEmpty {
        Text("No commits yet.")
  • Code after:
    if let failure = history.failure {
        GitFailureNotice(failure) { history.reload() }
    }
    if showsEmpty {
        Text("No commits yet.")
    } else {
        GitCommitList(commits: history.commits, onOpen: onOpen)
    }
  • How was this fixed: The Commits tab uses an internal MarkdownGitRepositoryHistory view that observes the history model, displays GitFailureNotice with reload retry, retains the last commit list alongside the notice, and gates its empty message on failure == nil. Extended GitHistoryTests.testHistoryFailuresKeepTheLastListAndRetryClearsTheFailure to assert the view's empty-state predicate after initial failure, failed refresh and successful empty retry. GitHistoryTests.swift is outside the original PR diff and was extended because it already owns the failure/retry fixture. Verification:.
  • Confidence: 96%

Check Git normalization before using the raw-size shortcut (eve-bot-lovinka)

  • Context: The raw size of an unchanged CRLF or clean-filtered file can differ from its normalized Git blob size. Rejecting these candidates before hash-object prevented exact normalized moves from pairing in Swift and TypeScript.
  • Commit(s): d4b94d17a
  • Verdict: accepted
  • Code before:
    if (stat.isFile() && sizes.has(stat.size)) {
        candidates.push(path);
    }
  • Code after:
    const normalizing = await normalizingPaths(root, opts.untracked.filter((path) => !path.includes("\n")));
    // Candidate loop:
    if (stat.isFile() && (sizes.has(stat.size) || normalizing.has(path))) {
        candidates.push(path);
    }
  • How was this fixed: Both implementations inspect core.autocrlf and effective NUL-delimited text, eol, filter, working-tree-encoding, ident and legacy crlf attributes. Normalizing paths bypass raw-size rejection and are hashed by Git. Unknown eligibility conservatively bypasses the shortcut too. Other paths retain size filtering. Swift also bypasses its file-identity hash cache for normalized paths, since filter configuration and attributes can change without file writes. Extended the shared contract-worktree-renames.json with three filesystem cases for core.autocrlf CRLF, text/eol CRLF and a size-changing clean filter. Both existing language test files consume those cases; Swift additionally checks changing only clean-filter configuration invalidates the prior normalized hash. Added GitWorktreeRenamesTests.testNormalizationCasesMatchTheCLIContract. Verification:.
  • Confidence: 93%

Restore the repository commit-log failure notice (eve-bot-lovinka)

  • Context: Moving repository history into its own tab dropped the failure notice and retry action. This is the same underlying defect as t1.
  • Commit(s): d4b94d17a
  • Verdict: accepted
  • Code before:
    if repoLog.loaded && repoLog.commits.isEmpty {
        Text("No commits yet.")
  • Code after:
    if let failure = history.failure {
        GitFailureNotice(failure) { history.reload() }
    }
  • How was this fixed: Addressed together with t1 in the shared repository-history view. It renders the model failure with a reload action and suppresses the successful-empty message during failure. The extended existing GitHistoryTests failure/retry test covers initial failure, retained commits on refresh failure and successful empty retry. That test file is outside the original diff because it owns the relevant fixture. Verification:.
  • Confidence: 96%

Threads: t1 accepted · t2 accepted · t3 accepted

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🟡 Review comments

a2b2b0e · 3 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟡 Medium 3

ℹ️ This pass reviewed the full PR diff: the incremental delta since f7c6aedb was unusable after a base-branch merge saturated GitHub's compare API (300+ files, 271 of them merge noise, list truncated).

Blast radius

  • Keyed (+) → 12 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Review/ReviewSessionState.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubWindow.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubSessionDetail.swift, +9 more
  • readKitFixtureJson (Δsig) → 9 dependent(s) outside this PR: src/du/lib/index/index.test.ts, src/doctor/tests/reclaim-analyzers.test.ts, src/doctor/tests/act.test.ts, +3 more
  • Recent (+) → 9 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubNotify.swift, src/macos/GenesisTools/Sources/Parts/Features/Settings/DaemonSettings.swift, src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownRecentsTests.swift, +6 more
  • before (+) → 8 dependent(s) outside this PR: src/browser-router/native/Router.swift, src/du/native/cloudctl.swift, src/macos/GenesisTools/Tests/ReviewQueueTests.swift, +5 more
  • file (+) → 8 dependent(s) outside this PR: src/browser-router/native/Router.swift, src/du/native/cloudctl.swift, src/macos/GenesisClickyPreview/Sources/PreviewWidgetRoster.swift, +5 more
  • runGit (+) → 8 dependent(s) outside this PR: src/scripts/lib/store.ts, src/scripts/commands/git.ts, src/gitlab/lib/git.ts, +5 more
  • find (+) → 7 dependent(s) outside this PR: src/macos/GenesisTools/Tests/HubTimelineRowIdentityTests.swift, src/macos/GenesisTools/Tests/LocalNetworkWatchTests.swift, src/macos/GenesisTools/Tests/HubLogicTests.swift, +4 more
  • activate (+) → 6 dependent(s) outside this PR: src/macos/GenesisTools/Tests/HubLogicTests.swift, src/macos/GenesisTools/Tests/HubPollGateTests.swift, src/macos/GenesisTools/Tests/ReviewSessionStateTests.swift, +3 more
  • apply (+) → 6 dependent(s) outside this PR: src/browser-router/native/Router.swift, src/macos/GenesisTools/Tests/HubTimelineRowIdentityTests.swift, src/macos/GenesisTools/Tests/HubInboxTimelineTests.swift, +3 more
  • apply (~) → 6 dependent(s) outside this PR: src/browser-router/native/Router.swift, src/macos/GenesisTools/Tests/HubTimelineRowIdentityTests.swift, src/macos/GenesisTools/Tests/HubInboxTimelineTests.swift, +3 more
  • +26 more changed symbol(s) with outside dependents

ℹ️ Claims vs diff — 8 claims could not be checked from the diff (c1, c2, c3, c4, c5, c6, c7, c8).

Comment thread src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitRepos.swift
Comment thread src/macos/GenesisKit/Sources/GenesisKit/Diff/Source/GitWorktreeRenames.swift Outdated
Comment thread src/macos/GenesisKit/Sources/GenesisKit/Diff/Views/GitRepoTable.swift Outdated

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🟡 Review comments

d4b94d1 · 2 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟡 Medium 2

ℹ️ This pass reviewed the full PR diff: the incremental delta since efcd6de6 was unusable after a base-branch merge saturated GitHub's compare API (300+ files, 270 of them merge noise, list truncated).

Blast radius

  • Keyed (+) → 12 dependent(s) outside this PR: src/macos/GenesisKit/Sources/GenesisKitCharts/HealthBar.swift, src/macos/GenesisKit/Sources/GenesisKitCharts/TreemapView.swift, src/macos/GenesisKit/Sources/GenesisKit/Markdown/Window/MarkdownDocumentsModel.swift, +9 more
  • readKitFixtureJson (Δsig) → 9 dependent(s) outside this PR: src/macos/commands/clones/reclaim.test.ts, src/macos/commands/clones/optimize.test.ts, src/macos/lib/kit-fixtures.test.ts, +3 more
  • Recent (+) → 9 dependent(s) outside this PR: src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownRecentsTests.swift, src/macos/GenesisKit/Sources/GenesisKit/Widgets/AgentWidgetModels.swift, src/macos/GenesisKit/Sources/GenesisKit/Markdown/Document/VaultIndex.swift, +6 more
  • controls (~) → 8 dependent(s) outside this PR: src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownPathsTests.swift, src/macos/GenesisKit/Tests/GenesisKitTests/ImageViewerTests.swift, src/macos/GenesisKit/Tests/GenesisKitTests/RecordingTests.swift, +5 more
  • runGit (+) → 8 dependent(s) outside this PR: src/scripts/commands/git.ts, src/scripts/lib/store.ts, src/utils/git/origins/pr-fetch.test.ts, +5 more
  • file (+) → 7 dependent(s) outside this PR: src/browser-router/native/Router.swift, src/macos/GenesisClickyPreview/Sources/PreviewWidgetRoster.swift, src/macos/GenesisClickyPreview/Sources/ClickyPreview.swift, +4 more
  • Contract (+) → 6 dependent(s) outside this PR: src/dev-dashboard/contract/client.test.ts, src/benchmark/lib/baseline.ts, src/macos/commands/clones/optimize.ts, +3 more
  • header (+) → 6 dependent(s) outside this PR: src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownDocumentWindowTests.swift, src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownContentViewTests.swift, src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownBlocksTests.swift, +3 more
  • Identity (+) → 6 dependent(s) outside this PR: src/macos/GenesisKit/Sources/GenesisKit/Settings/KitSettingsBackend.swift, src/macos/GenesisKit/Sources/GenesisKit/Clicky/ClickyPacks.swift, src/macos/GenesisTools/Tests/VaultUnlockTests.swift, +3 more
  • Kind (+) → 5 dependent(s) outside this PR: src/macos/GenesisKit/Tests/GenesisKitTests/Markdown/MarkdownDocumentWindowTests.swift, src/macos/GenesisKit/Tests/GenesisKitTests/ForgeBadgeTests.swift, src/macos/GenesisKit/Tests/GenesisKitTests/Alerts/AlertPolicyTests.swift, +2 more
  • +25 more changed symbol(s) with outside dependents

ℹ️ Claims vs diff — 3 claims could not be checked from the diff (c4, c5, c7).

load()
let gitFolder = root.appendingPathComponent(".git").path
workingTree = DirectoryWatcher(paths: [root.path], latency: 1, fileEvents: false,
accepts: { $0 != gitFolder && !$0.hasPrefix(gitFolder + "/") }) { [weak self] _ in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚡ Performance | 🟡 Medium · confidence 99/100

⚠️ Potential issue

Normalize the Git-folder exclusion to the watcher's path spelling

The exclusion compares real-path root paths against paths that DirectoryWatcher has already rewritten. GitQueries.root resolves symlinks, so repositories under /tmp or /var have roots under /private/tmp or /private/var, while DirectoryWatcher.normalizedEventPath strips /private before calling accepts (DirectoryWatcher.swift:49,71–75). Consequently no .git event in those repositories matches this exclusion. Commits, fetches and other Git-only activity unnecessarily schedule the working-tree status/hash/date reads, including a second refresh after the existing Git-directory watcher. Normalize gitFolder with the same function before capturing it in accepts.

🧩 Analysis

Grep evidence: normalizedEventPath|let gitFolder = root|accepts: \{ \$0 != gitFolder|resolvingSymlinksInPath

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. For a repository under /tmp or /var the watcher hands accepts() a path without /private, so the .git exclusion never matches and git-only activity schedules an extra working-tree read. The cost is an extra refresh, not a wrong result; the fix normalizes gitFolder with DirectoryWatcher.normalizedEventPath and adds a test under /tmp. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.

const proc = Bun.spawn(["git", "-C", root, ...args], {
stdout: "pipe",
stderr: "pipe",
env: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Tests | 🟡 Medium · confidence 98/100

⚠️ Potential issue

Isolate fixture Git processes from inherited repository and user config

The new fixture runner copies the entire parent environment and only overrides author identity. In a hook or rebase --exec, inherited GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE can redirect these add, mv and commit calls away from the temporary directory into the outer checkout; user hooks, signing and template configuration are also still active. This violates the project rule to sandbox external user-data configuration read by tests. The repository already provides src/utils/git/test-repo.ts:hermeticGitEnv (and isolatedGitEnv), which removes repository selectors and disables global/system config. Use that environment consistently for fixture mutations and the production readers exercised by these tests; the new Swift fixture helper likewise inherits user Git configuration and should be isolated.

🧩 Analysis

Grep evidence: process\.env|function hermeticGitEnv|function isolatedGitEnv|GIT_INDEX_FILE|GIT_CONFIG_GLOBAL

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. The fixture copies the whole environment, so GIT_DIR or GIT_INDEX_FILE from a hook would point its commands at the outer checkout. The fix uses hermeticGitEnv from src/utils/git/test-repo.ts for the fixture and the readers it exercises. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.

genesiscz added a commit that referenced this pull request Oct 26, 2026
…ounts follow an edit that keeps the same git status, and the table measures its row height on the first file row
genesiscz added a commit that referenced this pull request Oct 26, 2026
@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 04:33 from GPT-6.1 Sol judged by Claude Opus 5.5 (fable-judge)

Round 3. 3 unresolved Eve threads: 2 fixed with small source changes, 1 already addressed by the verified round-2 commit d4b94d1. No findings deferred. Fixed in 0b55281; the lead ran the tests and committed.

Refresh content-derived followers when porcelain status stays equal (eve-bot-lovinka)

  • Context: A second external edit to an already-modified file advances repository reads but not generation. File state and tree decorations followed generation, leaving their line counts stale even though the Changes list refreshed.
  • Commit(s): 0b55281b5
  • Verdict: accepted
  • Code before:
    let generations = skipFirst ? model.$generation.dropFirst().eraseToAnyPublisher() : model.$generation.eraseToAnyPublisher()
    repo = generations.sink { [weak self] _ in
  • Code after:
    let changes = contentChanges ? model.$reads.eraseToAnyPublisher() : model.$generation.eraseToAnyPublisher()
    let updates = skipFirst ? changes.dropFirst().eraseToAnyPublisher() : changes
    repo = updates.sink { [weak self] _ in
  • How was this fixed: GitRepoFollower gains an opt-in contentChanges parameter. GitFileStateModel and GitTreeDecorationsModel opt into the existing reads publisher; history and the other followers retain generation-based invalidation. This is a small source fix in the same stale-derived-data class as round 1. Extended GitReposTests.testTheFileStateFollowsAStageWithoutBeingAsked with a second external edit while status stays modified, asserting both file and tree additions change from 1 to 3 while status and generation remain equal. Its existing stage assertion now expects the three added lines. Verification:.
  • Confidence: 94%

Normalized blob hashes already bypass the file-identity cache (eve-bot-lovinka)

  • Context: The finding describes the old unconditional hash-cache lookup. The current head includes the round-2 normalization fix, which also bypasses cached hashes for paths affected by Git clean conversion.
  • Verdict: already done
  • How was this fixed: Already addressed in d4b94d1, confirmed by reading that commit's diff and current source. GitWorktreeRenames.swift:118-134 reads effective core.autocrlf and attributes; line 157 invokes that eligibility read on every find call. Line 169 permits cached OIDs only for paths absent from the current normalizing set, regardless of whether their size matches, and line 183 prevents caching normalized results. A newly added conversion attribute therefore bypasses an earlier raw hash immediately; changing a configured filter continues to bypass the cache; removing conversion restores raw hashing, for which the retained raw identity cache remains valid. Unknown eligibility also bypasses caching. GitWorktreeRenamesTests.swift:149-176 already covers a clean-filter configuration change without rewriting the destination. No additional source change is needed. Verification:.
  • Confidence: 98%

Defer row-height measurement until an actual file row appears (eve-bot-lovinka)

  • Context: A panel restored with folded sections could permanently cache the shorter heading height. Expanding a section then constrained file rows to that height.
  • Commit(s): 0b55281b5
  • Verdict: accepted
  • Code before:
    if coordinator.rowHeight == nil, let first = rows.first(where: { $0.kind == .file }) ?? rows.first {
  • Code after:
    if coordinator.rowHeight == nil, let first = rows.first(where: { $0.kind == .file }) {
  • How was this fixed: Removed the fallback to the first non-file row. The table uses its initial default height while only headings or folders exist and keeps the cached measurement unset until the first actual file row appears. This is a one-line source fix. Added GitRepoPanelKeyboardTests.testAFoldedTableMeasuresItsHeightWhenAFileFirstAppears, which mounts the table in a never-ordered-front window with a folded heading, expands it, and checks the newly visible file is measured rather than retaining the initial height. The test deliberately uses different heading/file typography to make the distinction observable. Verification:.
  • Confidence: 95%

Threads: t1 accepted · t2 already done · t3 accepted

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — ✅ Approved

5a5e7a2 · 1 actionable finding · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🔵 Low 1

Blast radius

  • panel (+) → 9 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/OcrReuse.swift, native/ax-tool/SnapshotSupport/DrawnCheck.swift, native/ax-tool/SnapshotSupport/WorkflowArguments.swift, +6 more
  • table (+) → 6 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/BulkHierarchy.swift, src/macos/GenesisTools/Sources/Host/VaultRecipient.swift, src/macos/GenesisTools/Sources/Host/App/GenesisToolsApp.swift, +3 more
  • graph truncated by caps — dependents above are a lower bound

let deadline = Date().addingTimeInterval(3)
while mounted.numberOfRows != 2, Date() < deadline {
host.layoutSubtreeIfNeeded()
try await Task.sleep(for: .milliseconds(20))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Tests | 🔵 Low · confidence 78/100

⚠️ Potential issue

Use an event-driven wait for the hosted table update

The new regression test polls the hosted table every 20 ms, repeatedly forcing layout until SwiftUI applies the update. This directly violates the project review-memory rule “Give every wait a deadline and expiry outcome; use event-driven waits, avoid … sub-100-ms polling.” The three-second deadline bounds the wait, but does not address the prohibited polling. Fulfill an XCTest expectation from the representable/table update (or an injected update callback) and await it with a timeout before asserting the row count and height.

🧩 Analysis

Grep evidence: Task.sleep\(for: \.milliseconds\(20\)\)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. The test should wait on an expectation fulfilled by the table update, keeping the 3 s deadline. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.

@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 04:41 from Claude Opus 5.5 judged by Claude Opus 5.5 (fable-judge)

After round 3, on 5a5e7a2. 2 new eve threads (both Medium): valid, deferred under the three-round limit; their threads stay open. No code change in this pass.

The .git exclusion compares a real path with the watcher's normalized path (eve-bot-lovinka)

  • Context: Valid by reading: GitRepos.swift:115-117 builds gitFolder from the real root, while DirectoryWatcher.swift:49 passes paths through normalizedEventPath (lines 71-75), which strips /private.
  • Verdict: belongs to another PR
  • Why: Valid. For a repository under /tmp or /var the watcher hands accepts() a path without /private, so the .git exclusion never matches and git-only activity schedules an extra working-tree read. The cost is an extra refresh, not a wrong result; the fix normalizes gitFolder with DirectoryWatcher.normalizedEventPath and adds a test under /tmp. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.
  • Confidence: 92%

The fixture's git calls inherit the parent's repository and user config (eve-bot-lovinka)

  • Context: Valid by reading: worktree-renames.test.ts:55-56 spreads process.env into the fixture's git environment and overrides only the author identity.
  • Verdict: belongs to another PR
  • Why: Valid. The fixture copies the whole environment, so GIT_DIR or GIT_INDEX_FILE from a hook would point its commands at the outer checkout. The fix uses hermeticGitEnv from src/utils/git/test-repo.ts for the fixture and the readers it exercises. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.
  • Confidence: 90%

Threads: t1 belongs to another PR · t2 belongs to another PR

genesiscz added a commit that referenced this pull request Oct 26, 2026
…ounts follow an edit that keeps the same git status, and the table measures its row height on the first file row
genesiscz added a commit that referenced this pull request Oct 26, 2026
@genesiscz
genesiscz force-pushed the feat/2026-10-25-repo-panel branch from 359497d to a935db9 Compare October 26, 2026 05:04
@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 06:05 from Claude Opus 5.5 judged by Claude Opus 5.5 (fable-judge)

After round 3, on the restacked head. 1 more eve thread (Low): deferred under the three-round limit; its thread stays open.

The table-height test polls every 20 ms instead of waiting on an event (eve-bot-lovinka)

  • Context: Valid: the test polls with a 3 s deadline; the wait is bounded but not event-driven.
  • Verdict: belongs to another PR
  • Why: Valid. The test should wait on an expectation fulfilled by the table update, keeping the 3 s deadline. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.
  • Confidence: 85%

Threads: t1 belongs to another PR

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🟡 Review comments

359497d · 2 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟡 Medium 1
🔵 Low 1

ℹ️ This pass reviewed the full PR diff: the incremental delta since 5a5e7a21 was unusable after a base-branch merge saturated GitHub's compare API (300+ files, 266 of them merge noise, list truncated).

Blast radius

  • Keyed (+) → 12 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Host/Notify.swift, src/macos/GenesisTools/Sources/Parts/Hub/Review/ReviewSessionState.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubSessionDetail.swift, +9 more
  • Key (~) → 9 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/CursorFeedback.swift, native/ax-tool/Sources/commands/Harness.swift, native/ax-tool/Sources/commands/Annotate.swift, +6 more
  • pair (+) → 9 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/OcrReuse.swift, native/ax-tool/SnapshotSupport/VisualCapture.swift, native/ax-tool/SnapshotSupport/FrameSampler.swift, +6 more
  • panel (−) → 9 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/OcrReuse.swift, native/ax-tool/SnapshotSupport/WorkflowArguments.swift, native/ax-tool/SnapshotSupport/WindowEvents.swift, +6 more
  • readKitFixtureJson (Δsig) → 9 dependent(s) outside this PR: src/doctor/tests/reclaim-analyzers.test.ts, src/doctor/tests/act.test.ts, src/du/lib/index/index.test.ts, +3 more
  • Recent (+) → 9 dependent(s) outside this PR: src/macos/GenesisTools/Sources/Parts/Features/Settings/DaemonSettings.swift, src/macos/GenesisTools/Sources/Parts/Hub/Hub/HubNotify.swift, src/macos/GenesisKit/Sources/GenesisKit/Flow/FlowWidget.swift, +6 more
  • controls (~) → 8 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/HierarchySource.swift, native/ax-tool/Sources/commands/Harness.swift, native/ax-tool/Sources/IsolatedCapture.swift, +5 more
  • find (+) → 8 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/WindowChoice.swift, native/ax-tool/SnapshotSupport/SnapshotToken.swift, native/ax-tool/SnapshotSupport/SnapshotDigest.swift, +5 more
  • runGit (+) → 8 dependent(s) outside this PR: scripts/ci/pre-commit-hook.test.ts, src/gitlab/lib/git.ts, src/scripts/lib/store.ts, +5 more
  • update (~) → 7 dependent(s) outside this PR: native/ax-tool/SnapshotSupport/OcrReuse.swift, native/ax-tool/SnapshotSupport/IsolatedComposition.swift, native/ax-tool/SnapshotSupport/PermissionRefusal.swift, +4 more
  • +25 more changed symbol(s) with outside dependents

ℹ️ Claims vs diff — 7 claims could not be checked from the diff (c1, c2, c3, c4, c5, c6, c8).

Not line-anchorable (review without the full diff context)

  • 🔵 Low src/macos/GenesisKit/Sources/GenesisKit/Diff/Views/GitRepoPanel.swift:897 · confidence 90/100 — Keep relative file ages ticking while the repository is idle: The new age label computes a plain String from Date() only when its hosted cell is rebuilt. Unlike the previous LiveAgo label, it has no clock. Equal status/date reads deliberately do not rebuild cells, so a file shown as seconds old can keep that label indefinitely, even across manual refreshes that find unchanged dates. Scrolling it out and back or changing the sort makes the age jump. Keep a clock local to the age view (as LiveAgo/LiveTime does), without invalidating the whole table.

stderr: "pipe",
env: { ...env.getProcessEnv(), GIT_OPTIONAL_LOCKS: "0" },
});
const [stdout, stderr, code] = await Promise.all([

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Quality | 🟡 Medium · confidence 93/100

⚠️ Potential issue

Bound the rename-query subprocess waits

runGit awaits both output streams and proc.exited without a deadline or cancellation. This is now on the tools git changes read path, and hash-object intentionally invokes configured clean filters: a stuck filter can prevent the command from ever returning, rather than falling back to the original status entries. This violates the project review rule that every wait has a deadline and defined expiry outcome. Add a bounded subprocess lifetime, terminate/reap it on expiry (including filter descendants as appropriate), and return the existing failure result so rename detection remains optional.

🧩 Analysis

Grep evidence: proc.exited|hash-object|pairStatusRenames\(root, status.entries\)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. runGit should end a subprocess after a deadline, reap it, and return the existing failure result, so rename detection stays optional. A hang needs a clean filter that never exits; with none configured the calls end on their own. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.

@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 06:06 from Claude Opus 5.5 judged by Claude Opus 5.5 (fable-judge)

After round 3, on the restacked head. 1 more eve thread (Medium): deferred under the three-round limit; its thread stays open.

The rename query's git subprocesses have no deadline (eve-bot-lovinka)

  • Context: Valid by the description: runGit awaits the streams and the exit with no deadline, and hash-object runs configured clean filters.
  • Verdict: belongs to another PR
  • Why: Valid. runGit should end a subprocess after a deadline, reap it, and return the existing failure result, so rename detection stays optional. A hang needs a clean filter that never exits; with none configured the calls end on their own. The pull request has had its three fix rounds, so this finding waits for a follow-up, and this thread stays open until then.
  • Confidence: 85%

Threads: t1 belongs to another PR

@genesiscz
genesiscz changed the base branch from feat/2026-10-25-markdown-viewer-ux to master October 26, 2026 05:06
…oved away opens its deleted diff instead of "not in a git repository", a folder moved outside git shows as renames in the repository panel, the Changes pane and the hub's working-tree diff, and the Repository tab keeps the last repository for a file outside one

- GitQueries.root(of:) starts git in the nearest folder that exists: git cannot start in a deleted file's folder when the whole folder moved, so every lookup, history, diff and file state of such a file said "This file is not in a git repository" (Martin, 2026-10-25: compiler.ts in a moved perf-deep folder).
- GitWorktreeRenames pairs a deleted file with an untracked file of the same blob (same name, then most equal trailing folders, then leading ones, then byte order; empty files never pair). Git itself pairs a rename only when both sides are in the index. Used by GitQueries.repoStatus (repository panel, tree marks) and GitWorkingTreeSource (hub review, Changes); the Changes pane reads a moved file's old side at its old path and says "Moved from … without changes".
- Real vault (650 deleted, 4,906 untracked): 142 renames, 508 real deletions stay deleted; 98-165 ms wall, 58-65 ms thread CPU off the main thread (GIT_RENAMES_REAL=<repo> swift test --filter GitWorktreeRenamesTests). Git's own detection through a scratch index took 3.9 s.
- The cases live in GenesisKitFixtures/Git/contract-worktree-renames.json; the CLI's twin passes the same file.
…nto renames dated by the new file, instead of 650 deletions counted as touched in the last hour

pairWorktreeRenames / findWorktreeRenames / pairStatusRenames in @genesiscz/utils/git, the twin of GenesisKit's GitWorktreeRenames: both pass GenesisKitFixtures/Git/contract-worktree-renames.json. Real vault: 650 D + 4,925 untracked → 142 R + 508 D, 50-92 ms wall, 29 ms CPU.
…ollow edits by other programs, at most every 3 s, instead of waiting for the next git event while the panel's Last update showed a note from 5 h ago

GitRepoModel watches its working tree (DirectoryWatcher, folder events, 1 s latency, the .git folder excluded) and refreshes no sooner than workingTreeInterval (3 s) after the last read, so an agent writing a file a second costs one status every few seconds. Before, only git's folder events and the viewer's own saves refreshed it (Martin, 2026-10-25: 4,836 untracked files up to 1 min old, the panel's newest row 5 h 15 m). Test: a file written with no refresh call shows up; a planted break (watcher callback dropped) times out.
…al window with the notes as tabs and the git sidebar on Repository, measuring main-thread CPU, busy time, longest pass, bodies and publishes for open, unrelated publishes, tab switches, filter typing, sort, fold and Show more, status refresh and 45 pt scrolling, plus the off-main status read, dates and derived lists; env-gated (MARKDOWN_REPO_BENCH), JSONL out with a summary over every run, in-bench sample
…difference: the same unrelated publish, tab switch and status refresh with the git sidebar on Repository, on This file and closed, interleaved step by step (phase ab), since a sample cannot name the panel's share of SwiftUI's layout
…e on either sidebar tab (MARKDOWN_REPO_BENCH_SAMPLE_TAB=file), so a sample of This file is the baseline for one of Repository
…wakes no follower unless git's folder, a save or a retry asked, status lookups by path are O(1), moved files are hashed once per file identity, and a moved file's status says "Moved from …"

- GitRepoModel.generation bumps only for a changed status or a read git's own folder, a save or a retry asked for; working-tree events read with wake: false. A new `reads` counter bumps on every read for the repository panel's dates. Every wake reloads each open note's agent blame and the panel; before, every read woke them (gt-native-fixes: blame is one run per repository at most every 5 s, now fewer).
- GitRepoStatus.entry(for:) uses a path index built with the status (off main): GitFileMark walked 5,650 entries per render, 30 samples in a sort sample of the repository panel.
- GitWorktreeRenames remembers blob ids by (inode, size, mtime ns), bounded at 50,000: a read no longer hashes the same 222 candidates again.
- GitQueries.fileState pairs an untracked file with a deleted one (tracked-only status, one hash): the status bar says "Moved from <old>" instead of "new file, not in git".
Tests: GitWorktreeRenamesTests.testAMovedFilesStateSaysWhereItCameFrom; GitReposTests (the empty-commit wake still holds).
…pKit table over the sidebar and refill in place, the sidebar and panel skip unrelated model publishes, an equal git read changes nothing, and Last update is one newest-first list; scroll 9.4 → 4.5 ms per step, publish 22.9 → 8.4 ms, tab switch 142 → 92 ms, equal refresh 134 → 49 ms, sort 68 → 39 ms on a 5,650-entry vault

Bench MarkdownRepoPanelBenchTests (vault, 39 tabs, debug, main-thread CPU medians; before = 4 rounds at 33430fc + bench, after = 4 rounds at the first table build, sort/filter/fold from one probe of this commit). Report: the notes vault, folder 2026-10-25-RepositoryPanel/.
- GitRepoTable: an NSTableView of hosted SwiftUI cells laid over the sidebar (MarkdownOverlaySlot, as the file tree). The LazyVStack re-placed its rows inside the window root's layout on every scroll step (743 of 984 busy samples under MarkdownRootHostingView.layout, 0 bodies). Scroll 9.4 → 4.5 ms, with a 600-row folder open 12.2 → 4.0 ms.
- One row height and in-place refills (enumerateAvailableRowViews) instead of reloadData: a reload rebuilt every row view, re-running AppKit's key-view loop and moving each SwiftUI host into the window (2,000 of 2,172 busy samples of a sort). Sort 68 → 39 ms, filter clear 72 → 70 ms, fold open 56 → 74 ms (one run), filter key 56 → 72 ms (every matching file is listed now).
- No Show more pages: the table builds only the rows on screen.
- MarkdownGitSidebar and GitRepoPanel are Equatable (closures excluded) and the sidebar no longer observes the documents model: publish 22.9 → 8.4 ms (panel share +13.9 → +0.2), tab switch 142 → 92 ms.
- No .focusable()/.focused over the rows (FocusBridge.updateDefaultKeyViewLoop: 26% of opening the tab, 31.6 ms per tab switch); the keyboard is the table's, same order and keys (Esc ignored when nothing is keyed).
- GitRepoPanelLists: dates by lstat (76 → 13 ms off main), one off-main pass, derive only when status, dates or inputs changed, publish only when the lists changed; equal refresh 134 → 49 ms main.
- Last update: changes, untracked files and untracked folders (by newest file) in one list newest first (Martin: the newest change row was 5 h old while untracked files were a minute old). Rename rows' tooltip "old → new".
- Durable profiling: spans git.panel.derive, git.panel.table; PerfMainBusy git.panel.open/sort/filter/fold/untracked; RenderProbe git.row.
Tests: keyboard order and keys, Last update order, equal read publishes nothing, a new root drops late dates, reveal lookup; bench adapted to the new types.
…on screen through a reload of the same diff instead of flashing away on every status refresh of a busy repository

GitFileDiffModel keeps the shown result while it reloads the same question (file, scope, pick, rename, compare) and shows loading only for a new one. Martin, 2026-10-25: "flashes every few seconds" in GenesisTools' own repository, where other sessions' git activity refreshes the status often. Test: three reloads of an empty diff never pass through .loading; the old code fails it with loading/empty flips.
…d's row with its own ring instead of the system focus ring, which the scroll view clipped and drew off the rows

Martin, 2026-10-25: the blue box around the first file sat above and beside the row. The list keeps its Up/Down keys; .focusEffectDisabled() drops the clipped ring, and the picked row draws an accent outline while the list has focus.
…d Commits tabs: Changes is the former Repository tab with its files only, Commits lists the repository's last 50 commits, and the changes table rebuilds only the cells whose row changed

- Martin, 2026-10-25: the Repository tab must show the changed files as before; commits get a tab of their own. The stored tab value "repo" opens Changes, so a saved choice keeps working.
- GitRepoTable rebuilds only the row views whose row changed (by id after an animated insert/remove, by index after an in-place refill), and the open file (revealPath) is no longer part of the cells' revision: a tab switch rebuilt every visible cell, +51 ms per switch in the 4-round after-run (ab.tabs repo 129 ms vs This file 78 ms); now only the old and new open file's rows are refreshed.
… and Untracked sections

A click on a heading, or Return, Left and Right on it, folds and unfolds its section; the fold survives a restart
(`genesiskit.gitRepoPanel.folded`). The heading carries a chevron, a tooltip, the header trait and
Expanded/Collapsed for VoiceOver. A filter shows its matches in every section, as it opens every folder.
The keyboard now stops on headings too, so a section can be folded without the mouse.

Busy time of a fold is written to perf.log as `git.panel.section`.
Tests: GitRepoPanelKeyboardTests (key order with headings, Left/Right/Return on a section, a folded section in both
layouts keeps only its heading).
…o a rewrite that keeps the size and the mtime is hashed again

The performance skill's freshness rule: a file's identity is inode, size, mtime and ctime; the mtime alone misses a
rewrite that puts it back. Test: GitWorktreeRenamesTests.testARewriteThatKeepsSizeAndMtimeIsHashedAgain.
… hosted-cell tables without reloadData, Equatable children, wake-on-change counters, git from a deleted folder, renames as one row, one Last update list, foldable sections, size-then-hash pairing, event sources that miss writers
…finding, foldable sections only for long lists with the outline keys, and the viewer rule says the change lists have no pages since 2026-10-25
…: Left on a file or a closed folder keys the row it belongs to, Left folds an open folder or section, Right opens a folded one (gt-native-ux's review)
…section, rename identity uses every candidate, date invalidation waits for its result, and staged files keep their index paths
…e it became a table, so the history failure notice from the restack goes
…alize line endings and clean filters before pairing, and the file history follows the review
…ounts follow an edit that keeps the same git status, and the table measures its row height on the first file row
@genesiscz
genesiscz force-pushed the feat/2026-10-25-repo-panel branch from a935db9 to f010b18 Compare October 26, 2026 05:09
@genesiscz
genesiscz merged commit f010b18 into master Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant