diff --git a/UPSTREAM.md b/UPSTREAM.md index de31c94..dee7035 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -1,7 +1,15 @@ # Fork ledger -What this fork changes relative to [nilbuild/diffity](https://github.com/nilbuild/diffity), so any -of it can be extracted as a pointed upstream pull request later. +What this fork changes relative to [nilbuild/diffity](https://github.com/nilbuild/diffity). + +**This is no longer aimed at upstream.** Decided 2026-08-28: upstream has pull requests sitting +uncommented since May, and this fork has diverged past the point where a pointed patch would still +apply — the review loop, the live agent protocol, session carry-forward and the idle lifecycle are +all ours. The record below stays because it explains where the code came from and why the +arrangement is what it is, not because anything is queued to be offered. + +What that changes in practice: keeping a thing because it might be upstreamable is no longer a +reason. Judge it on whether this fork uses it. ## How this fork is arranged @@ -21,8 +29,8 @@ of it can be extracted as a pointed upstream pull request later. Everything up to and including PR #24 was merged with merge commits, before this arrangement — that part of the history is mixed, and those branches still exist. -Nothing here is NaturalCycles-specific — all of it is upstreamable as-is. When that changes, -fork-local work goes in its own section below so it never lands in an upstream patch by accident. +The `Fork-local` section below predates the decision above; with upstream retired the distinction +it drew no longer does any work. ## Upstream pull requests merged in @@ -42,7 +50,7 @@ Merged unmodified, so they can simply be dropped once upstream lands them. same `--no-ext-diff` more broadly, and the two call sites #21 additionally covered are handled in our own change on #3. -## Ours, offerable upstream +## Ours (was: offerable upstream) | Fork PR | Branch | Base | What | Notes for upstreaming | | --- | --- | --- | --- | --- | diff --git a/package-lock.json b/package-lock.json index 7c24cb8..8d2d629 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8452,7 +8452,7 @@ }, "packages/cli": { "name": "diffity", - "version": "0.9.17", + "version": "0.9.18", "license": "MIT", "dependencies": { "commander": "^14.0.3", @@ -8476,7 +8476,7 @@ }, "packages/git": { "name": "@diffity/git", - "version": "0.9.17", + "version": "0.9.18", "devDependencies": { "@types/node": "^25.5.0", "typescript": "^5.9.3", @@ -8485,7 +8485,7 @@ }, "packages/github": { "name": "@diffity/github", - "version": "0.9.17", + "version": "0.9.18", "dependencies": { "@diffity/parser": "*" }, @@ -8497,7 +8497,7 @@ }, "packages/parser": { "name": "@diffity/parser", - "version": "0.9.17", + "version": "0.9.18", "devDependencies": { "typescript": "^5.9.3", "vitest": "^4.1.0" @@ -8505,7 +8505,7 @@ }, "packages/ui": { "name": "@diffity/ui", - "version": "0.9.17", + "version": "0.9.18", "dependencies": { "@react-router/node": "^7.13.2", "@tailwindcss/vite": "^4.2.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 4112d5e..6f29f71 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "diffity", - "version": "0.9.17", + "version": "0.9.18", "description": "GitHub-style git diff viewer in the browser", "type": "module", "bin": { diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index fa03757..207bdff 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -18,9 +18,6 @@ import { getUntrackedDiff, getRepoInfo, getFileContent, - getStagedFiles, - getUnstagedFiles, - getRecentCommits, getFileLineCount, resolveBaseRef, resolveDiffArgs, @@ -564,46 +561,7 @@ export function startServer(options: ServerOptions): Promise { return; } - if (pathname === '/api/overview') { - try { - const staged = getStagedFiles(); - const unstaged = getUnstagedFiles(); - const untracked = getUntrackedFiles(); - - const fileMap = new Map(); - for (const f of staged) { - fileMap.set(f, 'staged'); - } - for (const f of unstaged) { - fileMap.set(f, 'modified'); - } - for (const f of untracked) { - fileMap.set(f, 'added'); - } - const files = Array.from(fileMap.entries()).map( - ([path, status]) => ({ path, status }), - ); - - sendJson(res, { files }); - } catch (err) { - sendError(res, 500, `Failed to get overview: ${err}`); - } - return; - } - - if (pathname === '/api/commits') { - const count = parseInt(url.searchParams.get('count') || '10', 10); - const skip = parseInt(url.searchParams.get('skip') || '0', 10); - const search = url.searchParams.get('search') || undefined; - try { - const commits = getRecentCommits({ count, skip, search }); - sendJson(res, { commits, hasMore: commits.length === count }); - } catch (err) { - sendError(res, 500, `Failed to get commits: ${err}`); - } - return; - } if (pathname === '/api/diff-fingerprint') { const ref = url.searchParams.get('ref'); diff --git a/packages/git/package.json b/packages/git/package.json index 33f08a4..c86ea4a 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -1,6 +1,6 @@ { "name": "@diffity/git", - "version": "0.9.17", + "version": "0.9.18", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/packages/git/src/exec.ts b/packages/git/src/exec.ts index 4a483ce..e0a9f29 100644 --- a/packages/git/src/exec.ts +++ b/packages/git/src/exec.ts @@ -75,10 +75,3 @@ export function gitLines(args: string[]): string[] { return output.split('\n'); } -export function execLines(cmd: string): string[] { - const output = exec(cmd); - if (!output) { - return []; - } - return output.split('\n'); -} diff --git a/packages/git/src/index.ts b/packages/git/src/index.ts index e1a1783..880e74e 100644 --- a/packages/git/src/index.ts +++ b/packages/git/src/index.ts @@ -3,7 +3,7 @@ export type { RefCapabilities } from './repo.js'; export { isGitRepo, getRepoRoot, getRepoName, getCurrentBranch, getRepoInfo, getHeadHash, getDiffityDir, getDiffityDirPath, isDataDirUntracked, getRefCapabilities, isValidGitRef } from './repo.js'; export { getDiff, getDiffFiles, getDiffStat, getDiffStatForRef, getRenameStatus, getUntrackedFiles, getUntrackedDiff, getFileContent, getFileLineCount, getMergeBase, normalizeRef, resolveBaseRef, resolveThroughUpstream, resolveDiffArgs, resolveRef, revertFile, revertHunk, WORKING_TREE_REFS } from './diff.js'; export type { RefDiffArgs } from './diff.js'; -export { getStagedFiles, getUnstagedFiles, isDirty } from './status.js'; +export { isDirty } from './status.js'; export { getRecentCommits } from './commits.js'; export { readRepoConfig, resolveDataDir, REPO_CONFIG_FILE, DEFAULT_SEVERITIES } from './config.js'; export type { RepoConfig, ReviewConfig } from './config.js'; diff --git a/packages/git/src/status.ts b/packages/git/src/status.ts index 59081c0..50196c6 100644 --- a/packages/git/src/status.ts +++ b/packages/git/src/status.ts @@ -1,12 +1,4 @@ -import { exec, execLines } from './exec.js'; - -export function getStagedFiles(): string[] { - return execLines('git diff --staged --name-only'); -} - -export function getUnstagedFiles(): string[] { - return execLines('git diff --name-only'); -} +import { exec } from './exec.js'; export function isDirty(): boolean { return exec('git status --porcelain').length > 0; diff --git a/packages/github/package.json b/packages/github/package.json index c2d4f2e..adc1406 100644 --- a/packages/github/package.json +++ b/packages/github/package.json @@ -1,6 +1,6 @@ { "name": "@diffity/github", - "version": "0.9.17", + "version": "0.9.18", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/packages/parser/package.json b/packages/parser/package.json index 738750d..601dcfc 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@diffity/parser", - "version": "0.9.17", + "version": "0.9.18", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/packages/ui/package.json b/packages/ui/package.json index 0fe38c7..be4c14d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@diffity/ui", - "version": "0.9.17", + "version": "0.9.18", "type": "module", "private": true, "scripts": { diff --git a/packages/ui/src/components/layout/commit-list.tsx b/packages/ui/src/components/layout/commit-list.tsx deleted file mode 100644 index f7384f7..0000000 --- a/packages/ui/src/components/layout/commit-list.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useState, useCallback, useRef } from 'react'; -import { type Commit, fetchCommits } from '../../lib/api'; - -interface CommitListProps { - initialCommits: Commit[]; - initialHasMore: boolean; - onCommitClick: (hash: string) => void; -} - -export function CommitList(props: CommitListProps) { - const { initialCommits, initialHasMore, onCommitClick } = props; - const [commits, setCommits] = useState(initialCommits); - const [hasMore, setHasMore] = useState(initialHasMore); - const [loading, setLoading] = useState(false); - const [search, setSearch] = useState(''); - const searchTimeout = useRef>(null); - - const handleSearch = useCallback((value: string) => { - setSearch(value); - - if (searchTimeout.current) { - clearTimeout(searchTimeout.current); - } - - searchTimeout.current = setTimeout(async () => { - setLoading(true); - try { - const trimmed = value.trim(); - const page = await fetchCommits(0, 10, trimmed || undefined); - setCommits(page.commits); - setHasMore(page.hasMore); - } finally { - setLoading(false); - } - }, 300); - }, []); - - const loadMore = useCallback(async () => { - setLoading(true); - try { - const trimmed = search.trim(); - const page = await fetchCommits(commits.length, 10, trimmed || undefined); - setCommits((prev) => [...prev, ...page.commits]); - setHasMore(page.hasMore); - } finally { - setLoading(false); - } - }, [commits.length, search]); - - return ( -
-
- handleSearch(e.target.value)} - placeholder="Search commits..." - className="w-full text-sm bg-bg border border-border rounded-md px-3 py-1.5 text-text placeholder:text-text-muted focus:outline-none focus:border-accent" - /> -
- {commits.length === 0 ? ( -

- {search ? 'No matching commits' : 'No recent commits'} -

- ) : ( -
    - {commits.map((commit) => ( -
  • - -
  • - ))} -
- )} - {hasMore && ( -
- -
- )} -
- ); -} diff --git a/packages/ui/src/components/layout/dashboard.tsx b/packages/ui/src/components/layout/dashboard.tsx deleted file mode 100644 index 6fc1975..0000000 --- a/packages/ui/src/components/layout/dashboard.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useOverview } from '../../hooks/use-overview'; -import { useCommits } from '../../hooks/use-commits'; -import { useInfo } from '../../hooks/use-info'; -import { OverviewFileList } from './overview-file-list'; -import { CommitList } from './commit-list'; -import { CheckCircleIcon } from '../icons/check-circle-icon'; -import { PageLoader } from './skeleton'; - -interface DashboardProps { - onNavigate: (ref: string) => void; -} - -export function Dashboard(props: DashboardProps) { - const { onNavigate } = props; - const { data: overview, loading: overviewLoading, error } = useOverview(); - const { data: commitsPage, loading: commitsLoading } = useCommits(); - // `useInfo` suspends, so its data is there by the time this renders and there is no - // loading to ask about. Asking anyway read as undefined and contributed nothing. - const { data: info } = useInfo(); - - const anyLoading = overviewLoading || commitsLoading; - - if (error) { - return ( -
-
-

Failed to load overview

-

{error}

-
-
- ); - } - - if (anyLoading || !overview) { - return ; - } - - const isClean = overview.files.length === 0; - - return ( -
-
-
- {info?.name && ( - {info.name} - )} - {info?.branch && ( - - {info.branch} - - )} -
-
- -
-
- {isClean ? ( -
-
- -
-

Working tree is clean

-

No changes to display.

-
- ) : ( - onNavigate('work')} - /> - )} - -
-
-

Recent commits

-
- onNavigate(hash)} - /> -
-
-
-
- ); -} diff --git a/packages/ui/src/components/layout/overview-file-list.tsx b/packages/ui/src/components/layout/overview-file-list.tsx deleted file mode 100644 index be29a97..0000000 --- a/packages/ui/src/components/layout/overview-file-list.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { OverviewFile } from '../../lib/api'; - -interface OverviewFileListProps { - files: OverviewFile[]; - onViewAll: () => void; -} - -const STATUS_COLORS: Record = { - staged: 'text-added', - modified: 'text-changed', - added: 'text-added', -}; - -const STATUS_LABELS: Record = { - staged: 'S', - modified: 'M', - added: 'A', -}; - -export function OverviewFileList(props: OverviewFileListProps) { - const { files, onViewAll } = props; - - if (files.length === 0) { - return null; - } - - return ( -
-
-
-

Changed files

- - {files.length} - -
- -
-
    - {files.map((file) => ( -
  • - - {STATUS_LABELS[file.status]} - - - {file.path} - -
  • - ))} -
-
- ); -} diff --git a/packages/ui/src/hooks/use-commits.ts b/packages/ui/src/hooks/use-commits.ts deleted file mode 100644 index f591b54..0000000 --- a/packages/ui/src/hooks/use-commits.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { commitsOptions } from '../queries/commits'; - -export function useCommits() { - const { data, isLoading, error } = useQuery(commitsOptions()); - - return { - data: data ?? null, - loading: isLoading, - error: error?.message ?? null, - }; -} diff --git a/packages/ui/src/hooks/use-overview.ts b/packages/ui/src/hooks/use-overview.ts deleted file mode 100644 index 351e066..0000000 --- a/packages/ui/src/hooks/use-overview.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { overviewOptions } from '../queries/overview'; - -export function useOverview() { - const { data, isLoading, error } = useQuery(overviewOptions()); - - return { - data: data ?? null, - loading: isLoading, - error: error?.message ?? null, - }; -} diff --git a/packages/ui/src/lib/api.ts b/packages/ui/src/lib/api.ts index 4a3451f..c7ed874 100644 --- a/packages/ui/src/lib/api.ts +++ b/packages/ui/src/lib/api.ts @@ -81,27 +81,6 @@ export interface RepoInfo { review?: ReviewRun | null; } -export interface Commit { - hash: string; - shortHash: string; - message: string; - relativeDate: string; -} - -export interface OverviewFile { - path: string; - status: 'staged' | 'modified' | 'added'; -} - -export interface Overview { - files: OverviewFile[]; -} - -export interface CommitsPage { - commits: Commit[]; - hasMore: boolean; -} - export function fetchDiff(hideWhitespace: boolean, ref?: string): Promise { return apiFetch(buildUrl('/api/diff', { whitespace: hideWhitespace ? 'hide' : undefined, @@ -143,13 +122,7 @@ export function openInEditor(filePath: string, line?: number): Promise<{ ok: boo }); } -export function fetchOverview(): Promise { - return apiFetch('/api/overview'); -} -export function fetchCommits(skip = 0, count = 10, search?: string): Promise { - return apiFetch(buildUrl('/api/commits', { skip: String(skip), count: String(count), search })); -} export async function fetchSession(): Promise<{ id: string; ref: string; headHash: string } | null> { const res = await fetch('/api/sessions/current'); diff --git a/packages/ui/src/queries/commits.ts b/packages/ui/src/queries/commits.ts deleted file mode 100644 index 888f7c9..0000000 --- a/packages/ui/src/queries/commits.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { queryOptions } from '@tanstack/react-query'; -import { fetchCommits } from '../lib/api'; - -export function commitsOptions() { - return queryOptions({ - queryKey: ['commits'], - queryFn: () => fetchCommits(0, 10), - }); -} diff --git a/packages/ui/src/queries/overview.ts b/packages/ui/src/queries/overview.ts deleted file mode 100644 index cf15c2e..0000000 --- a/packages/ui/src/queries/overview.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { queryOptions } from '@tanstack/react-query'; -import { fetchOverview } from '../lib/api'; - -export function overviewOptions() { - return queryOptions({ - queryKey: ['overview'], - queryFn: fetchOverview, - }); -}