mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-03 13:24:41 +00:00
* feat(web-shell): add git commit history browser Add a read-only Git Log dialog to the Web Shell, accessible via /log command or the History tab in the Changes dialog. Full-stack implementation across core, daemon, SDK, and web-shell: - core: fetchGitLog (paginated commit list) and fetchGitCommitDetail (message body + per-file numstat) with 12 integration tests - daemon: GET /workspace/git/log and /workspace/git/log/commit routes with bound + qualified dual registration - SDK: DaemonGitLog/DaemonGitCommitDetail types and client methods - web-shell: GitLogDialog with commit list, expandable details, SHA copy icon, Load more pagination, and Changes/History tab switching in both dialogs * fix(web-shell): address review feedback on git log browser - Critical: use first-parent diff for merge commits (diff-tree without -c or explicit parent outputs nothing for merges) - Remove dead embedded prop from GitDiffDialog and GitLogDialog - Replace span role=button with aria-hidden for copy icon (a11y) - Add loadMore error feedback instead of silent catch - Refresh relative timestamps every 60s (useState + interval) - Remove dead branch param from subtitle i18n call * fix(web-shell): address R2 review feedback on git log browser - Use bounded split in parseLogFields (first 7 separators) to prevent subject containing literal \x1f from shifting the parents field - Add SHA hex format validation at daemon route layer (400 for invalid) - Add rendering branch for detail.available === false (error message instead of empty content) * fix(web-shell): count renamed files in commit detail + test git-log route & dialog Follows the R2 review-feedback commit (which fixed the bounded parse, the non-hex SHA 400, and the unavailable-detail render). Remaining items: - Commit detail counts renamed files. diff-tree is plumbing and does not honour diff.renames, so a `git mv` split into a delete + add pair (or an empty-path entry) instead of one file — understating filesCount / linesAdded / linesRemoved. Run diff-tree with -M and give the inline numstat parser the same pending-rename state machine as parseGitNumstat, so a rename is one file keyed by its new path. Covered by a real-repo rename test, plus a merge-commit test that locks the first-parent diff. - Tests for the two previously-untested modules: the workspace-git-log route (list shape, pagination clamping, sha-required + non-hex 400, trust gating) and GitLogDialog (all five list state paths, load-more offset + error, detail expand + both failure branches incl. available:false, and the relative-time render). * fix(web-shell): address R3 review feedback on git log browser - Fix timeAgo '0y ago' for commits ~360-364 days old (Math.max(1, ...)) - Add .catch() to clipboard writeText to prevent unhandled rejection - Reset loadMoreError on initial re-fetch (daemon reconnect) - Preserve prev.available in loadMore merge instead of overwriting - Add ARIA tab semantics (role=tablist/tab, aria-selected) to both Changes and History tab bars * fix(web-shell): address R4 review feedback on git log browser - Fix vacuous limit-clamp test: seed 3 commits, verify limit=2 returns 2 + hasMore, limit=0 clamps to 1 - Add CSS var fallbacks for --subtle-bg and --success-bg (dialog portals outside App.module.css scope) - Extract GIT_DIALOG_SWITCH_DELAY_MS constant with doc comment - Make copy-SHA control keyboard accessible (tabIndex, onKeyDown, aria-label instead of aria-hidden) * fix(web-shell): escape apostrophe in worktree welcome string * fix(web-shell): address git history review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(web-shell): mock git diff content in app tests Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): harden git log metadata parsing Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: wenshao <wenshao@example.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
211 lines
3.3 KiB
CSS
211 lines
3.3 KiB
CSS
.content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 50vh;
|
|
}
|
|
|
|
.placeholder {
|
|
padding: 24px 12px;
|
|
text-align: center;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.commitList {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.commitRow {
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.commitHeader {
|
|
display: flex;
|
|
align-items: stretch;
|
|
width: 100%;
|
|
}
|
|
|
|
.commitHeader:hover {
|
|
background: var(--subtle-bg, rgba(128, 128, 128, 0.06));
|
|
}
|
|
|
|
.commitToggle {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
padding: 8px 4px 8px 10px;
|
|
background: transparent;
|
|
border: 0;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
text-align: left;
|
|
color: inherit;
|
|
}
|
|
|
|
.commitSha {
|
|
flex-shrink: 0;
|
|
font-family: var(--font-mono, monospace);
|
|
font-size: 12px;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.copyBtn {
|
|
flex-shrink: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
align-self: stretch;
|
|
padding: 0 10px 0 6px;
|
|
border: 0;
|
|
border-radius: 3px;
|
|
background: transparent;
|
|
color: var(--muted-foreground);
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
transition: opacity 0.15s;
|
|
}
|
|
|
|
.commitHeader:hover .copyBtn,
|
|
.copyBtn:focus-visible {
|
|
opacity: 1;
|
|
}
|
|
|
|
.copyBtn:hover {
|
|
background: var(--muted);
|
|
}
|
|
|
|
.commitSubject {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.commitMeta {
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
color: var(--muted-foreground);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.commitRefs {
|
|
display: inline-flex;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.refTag {
|
|
padding: 0 5px;
|
|
border-radius: 4px;
|
|
background: var(--muted);
|
|
color: var(--muted-foreground);
|
|
font-size: 10px;
|
|
line-height: 16px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.refHead {
|
|
background: var(--success-bg, rgba(76, 175, 80, 0.12));
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.mergeIcon {
|
|
flex-shrink: 0;
|
|
color: var(--muted-foreground);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.commitDetail {
|
|
padding: 8px 10px 12px;
|
|
border-top: 1px solid var(--border);
|
|
background: var(--subtle-bg, rgba(128, 128, 128, 0.06));
|
|
}
|
|
|
|
.commitBody {
|
|
margin: 0 0 8px;
|
|
font-size: 12px;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.fileStats {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.fileStatHeader {
|
|
font-size: 11px;
|
|
color: var(--muted-foreground);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.fileStatRow {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
padding: 1px 0;
|
|
}
|
|
|
|
.statNums {
|
|
display: inline-flex;
|
|
gap: 6px;
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.statAdd {
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.statDel {
|
|
color: var(--error-color);
|
|
}
|
|
|
|
.fileStatPath {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.fileBinary {
|
|
color: var(--muted-foreground);
|
|
}
|
|
|
|
.hiddenNote {
|
|
padding: 4px 0;
|
|
color: var(--muted-foreground);
|
|
font-size: 11px;
|
|
}
|
|
|
|
.detailError {
|
|
padding: 8px 0;
|
|
color: var(--error-color);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.loadMore {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 10px;
|
|
margin-top: 4px;
|
|
background: transparent;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
font-size: 12px;
|
|
color: var(--muted-foreground);
|
|
text-align: center;
|
|
}
|
|
|
|
.loadMore:hover {
|
|
background: var(--subtle-bg, rgba(128, 128, 128, 0.06));
|
|
}
|