Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
232 commits
Select commit Hold shift + click to select a range
3c5220b
Side pane: find tabs by id without building CSS selectors
claude Oct 7, 2026
2200d12
UI system gate: scope the tool presentation styles and drop the stray…
claude Oct 7, 2026
58c8cf1
Side pane: a tab whose view fails to mount shows an error and a retry
claude Oct 7, 2026
e046c6e
Renderer: a side pane that fails to start no longer takes the chat do…
claude Oct 7, 2026
4bc631e
Side browser: no local file pages, and downloads need a real click
claude Oct 7, 2026
7da3ba3
Side pane tabs: arrow keys keep focus on the strip, Delete closes, vi…
claude Oct 7, 2026
6757b37
Plan detail: keyboard focus survives the page's redraws
claude Oct 7, 2026
c8b5975
Git page: the file context menu works from the keyboard
claude Oct 7, 2026
a53275a
Code viewer: the file button shows the file in the file manager
claude Oct 7, 2026
ba0f44d
Side pane resizer: keyboard resizing saves the width once, after the …
claude Oct 7, 2026
6afd608
Side pane: open/close animation, topic scope, dormancy quota and tab …
claude Oct 7, 2026
8d2d429
CI: run the side pane tests on every UI pull request
claude Oct 7, 2026
b6abbd0
Code viewer: render code in content-visibility chunks so big files op…
claude Oct 7, 2026
04195c7
Code viewer: ask before reading a file outside the workspaces
claude Oct 7, 2026
43f6334
Git service: never run the repo's own core.fsmonitor command
claude Oct 7, 2026
768de20
UI system gate: scope the tool presentation styles and drop the stray…
claude Oct 7, 2026
cb413eb
Side chat and plan: closing asks before deleting, message buttons res…
claude Oct 7, 2026
195452d
Terminal tab: OSC 8 links open in the side browser, Ctrl+C with a sel…
claude Oct 7, 2026
f4f61ba
Side pane: leave tab lookup and strip keyboard focus to the parallel …
claude Oct 7, 2026
7b4d765
Plan detail: move the composition guard below the focus bookkeeping
claude Oct 7, 2026
d490d9c
Side pane: share findByTabId's signature with PR #4
claude Oct 7, 2026
2cee0b8
Merge robustness and security fixes (#3) into the side pane integrati…
claude Oct 7, 2026
8c303f6
Merge review fixes (#4) into the side pane integration branch
claude Oct 7, 2026
bb9a89a
Contracts: regenerate the chat event graph after merging #3 and #4
claude Oct 7, 2026
4292711
Side pane: tab context menu returns focus to the tab
claude Oct 7, 2026
a1bc4c3
Git page: line counts and diffs follow later edits, subdirectory path…
claude Oct 7, 2026
efbdd43
Contracts: follow the dynamic listener sites the tool card presentati…
RoxyAsahi Oct 7, 2026
3b559e9
Code viewer: keep chunk placeholder height in the stylesheet, not inline
claude Oct 7, 2026
b956a70
Side pane: stop restyling the whole page on every DOM change
claude Oct 7, 2026
39c3e00
Source IPC: use the shared top-level window sender guard
claude Oct 7, 2026
ca369fa
Contracts: follow the sourceHandlers dynamic site to its new line
claude Oct 7, 2026
b0e4a02
Stop the tooltip bridge and turn navigator from keeping removed nodes…
claude Oct 7, 2026
2621979
UI system gate: scope the tool presentation styles and drop the stray…
claude Oct 7, 2026
0753805
UI system gate: scope the tool presentation styles and drop the stray…
claude Oct 7, 2026
ced244e
Merge remote-tracking branch 'origin/pr/side-pane-workbench' into cla…
claude Oct 7, 2026
7fbdc6f
Merge remote-tracking branch 'origin/pr/side-pane-workbench' into cla…
claude Oct 7, 2026
55a37fb
Command output tab: a printing command is re-read once a second with …
claude Oct 7, 2026
5649081
Merge #3's latest head into the side pane integration branch
claude Oct 7, 2026
842410b
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
31c0428
UI system gate: use the accent token for the pinned window control
claude Oct 7, 2026
ca9a375
Merge code viewer chunked rendering (#5) into the side pane integrati…
claude Oct 7, 2026
5b943b8
Contracts: regenerate the chat event graph after merging the latest #…
claude Oct 7, 2026
9421bb1
Merge leak fixes (#6) into the side pane integration branch
claude Oct 7, 2026
0aa0295
Tool card styles: pass the UI system gate the base branch fails
claude Oct 7, 2026
e20dbb4
UI system gate: use the accent token for the pinned window button
claude Oct 7, 2026
dcd3a73
Chat: open big assistants without waiting on side chat scans and heig…
claude Oct 7, 2026
295d957
Browser tab: a page playing video, even muted, is not put to sleep
claude Oct 7, 2026
6014f75
Side chats: restoring them on a topic switch no longer forces the pan…
claude Oct 7, 2026
ea899c9
Contracts: follow the message renderer listener site the pretext queu…
claude Oct 7, 2026
b44c092
Side pane: expand before mounting, and batch closes stop mounting doo…
claude Oct 7, 2026
0befa45
Side chat: View trajectory opens the child topic's calls and finds th…
claude Oct 7, 2026
02356c3
Merge remote-tracking branch 'origin/pr/side-pane-workbench' into cla…
claude Oct 7, 2026
e9df8a7
Merge #5's latest head into the side pane integration branch
claude Oct 7, 2026
f67b740
UI system gate: take the integration branch's tool card and component…
claude Oct 7, 2026
105c6dc
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
2eb5587
Contracts: regenerate the chat event graph after merging the latest #…
claude Oct 7, 2026
06961e4
Leak test: count listeners, intervals and observers that bypass own.*
claude Oct 7, 2026
b38aa8b
Code viewer picker: keyboard navigation and focus after opening a file
claude Oct 7, 2026
d322faf
Tests: replace source-text assertions with behaviour, drop CSS text pins
claude Oct 7, 2026
15af839
CI: run the 28 workbench tests no gate ran, and trigger on the files …
claude Oct 7, 2026
263b6c9
Tab overview: expose the arrow-key selection to screen readers
claude Oct 7, 2026
67cefcd
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
c15e707
Git graph: selecting a commit or loading more keeps the scroll position
claude Oct 7, 2026
decc1b3
Git view: keep focus on the same file across a pushed redraw
claude Oct 7, 2026
281471c
Sidebar: show every unsaved settings edit; debounce and harden topic …
claude Oct 7, 2026
eceb23e
Terminal: tell xterm about ConPTY, and skip the PowerShell handshake …
claude Oct 7, 2026
4b5862a
Git: drop an inherited GIT_DIR, and read parsed errors in English
claude Oct 7, 2026
8dc795a
UI system gate: scope the tool presentation styles and drop the stray…
claude Oct 7, 2026
fbb6911
Side pane: return to a topic's side chat, and keep side chat replies …
claude Oct 7, 2026
1ba83fb
UI system gate: take the integration branch's tool presentation and c…
claude Oct 7, 2026
6631589
Status panel: data refreshes no longer close the branch popover under…
claude Oct 7, 2026
49c1183
Trajectory tab: stay at the top while reading, and stop piling up cop…
claude Oct 7, 2026
cc1877d
Merge #3's latest head into the side pane integration branch
claude Oct 7, 2026
cd17495
Merge #7's latest head into the side pane integration branch
claude Oct 7, 2026
d565885
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
b5cc1a0
Merge #8's latest head into the side pane integration branch
claude Oct 7, 2026
30f09c5
Side terminal: type the workspace jump without pressing Enter
claude Oct 7, 2026
a286784
Side chat: a failed send gives the draft back even if the words were …
claude Oct 7, 2026
8c5fd7e
Side browser: make the more menu reachable by keyboard
claude Oct 7, 2026
3059799
Revert "UI system gate: take the integration branch's tool presentati…
claude Oct 7, 2026
fd66986
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
342714f
Terminal: canvas colours come from the mounted frame; PSReadLine redr…
claude Oct 7, 2026
9836871
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
0f4a3c2
Merge integration PR #2 (claude/project-thread-8tpfrr) into PR #3
claude Oct 7, 2026
3067235
Delete a conversation's model trajectory file when the conversation i…
claude Oct 7, 2026
1116ae7
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
b6066c2
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
465cee1
Side pane P2 batch J: floating ask button, side chat scroll intent, g…
claude Oct 7, 2026
6055b27
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
f2da276
Side pane: drop tabs owned by a deleted topic
claude Oct 7, 2026
1357c31
Git page: mount long change lists in batches
claude Oct 7, 2026
7ac8edf
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
d947bb0
topicListManager.js: restore CRLF line endings
claude Oct 7, 2026
1b0da7b
PowerShellExecutor.js: restore CRLF line endings
claude Oct 7, 2026
7d962eb
Side pane batch L: quiet startup restore, ordered Git reads, no page …
claude Oct 7, 2026
f0b92af
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
17c29af
Message file changes: one history index per mutation batch
claude Oct 7, 2026
9f9a449
gitService: coalesce concurrent status reads per repository
claude Oct 7, 2026
a9adc08
Merge #6's latest head into the side pane integration branch
claude Oct 7, 2026
1967832
Merge #5's latest head into the side pane integration branch
claude Oct 7, 2026
7a0a15c
Restore CRLF line endings on topicListManager.js and PowerShellExecut…
claude Oct 7, 2026
fa3f0ea
Merge #4's latest head into the side pane integration branch
claude Oct 7, 2026
c6e4ac9
Side pane batch N: recently closed per conversation, command output l…
claude Oct 7, 2026
02a47f9
Merge #4's batch N into the side pane integration branch
claude Oct 7, 2026
d6e6af2
Side pane batch O: layout save budget, no domain reactivation at quit
claude Oct 7, 2026
f066b99
Merge #4's batch O into the side pane integration branch
claude Oct 7, 2026
1a2c655
Floating ask button: reposition after scrolling settles
claude Oct 7, 2026
eaa2337
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
327dc0f
Merge #3's keyboard and terminal fixes into the side pane integration…
claude Oct 7, 2026
0b764a0
Merge origin/claude/project-thread-p17ix8 into the side pane integrat…
claude Oct 7, 2026
44d48b0
Merge origin/claude/project-thread-1zv9nc into the side pane integrat…
claude Oct 7, 2026
7c69db6
Merge integration PR #2's latest head into PR #3
claude Oct 7, 2026
e96c757
Run the jump-to-workspace quoting test in test:workbench
claude Oct 7, 2026
6f69591
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
028b501
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
ec388d1
Agent settings: keep an unsaved draft per Agent across switches
claude Oct 7, 2026
0ad7443
Code viewer: scroll line numbers with the code
claude Oct 7, 2026
b5dd2c5
Sidebar resizers: reachable by keyboard, one settings write per adjus…
claude Oct 7, 2026
ac7e4f8
Topic list: keyboard navigation and a keyboard-operable context menu
claude Oct 7, 2026
5a8e8fe
Side pane batch P: page residency budget, unmounted side chat close, …
claude Oct 7, 2026
a3b66c2
Merge integration PR #2 (e96c7578) into PR #3
claude Oct 7, 2026
af2ba43
Side pane: don't offer a side chat while a group is selected
claude Oct 7, 2026
9a4971f
Agent list: keyboard navigation
claude Oct 7, 2026
0455004
Side pane batch Q: code viewer keeps its place, concurrent asks keep …
claude Oct 7, 2026
23e05e1
Agent list: keep the search filter when the list is rebuilt
claude Oct 7, 2026
9d26983
Settings sections: one keyboard control per header, labels that point…
claude Oct 7, 2026
dbbf215
Side terminal: stop blinking the cursor
claude Oct 7, 2026
cb6ac0e
Agent list: a rejected catalog read shows an error, not a stuck spinner
claude Oct 7, 2026
d242f43
Side pane batch R: side chats sleep when idle and come back where the…
claude Oct 7, 2026
94d022d
Merge #4's batches P–R into the side pane integration branch
claude Oct 7, 2026
fab4ecd
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
2b4ff51
Agent list: version avatar URLs by mtime instead of re-downloading ev…
claude Oct 7, 2026
18ead3a
Merge #9's steady terminal cursor into the side pane integration branch
claude Oct 7, 2026
d3ae64a
Merge #7's sidebar keyboard and agent list fixes into the side pane i…
claude Oct 7, 2026
5f18a5b
Settings: rebuild only the changed form's dropdowns
claude Oct 7, 2026
ae9a99b
Side pane batch S: bounded terminal replay without per-chunk copies, …
claude Oct 7, 2026
3931887
Settings: bind MiMo director editors to the slot's own scope
claude Oct 7, 2026
85313c6
Main chat code blocks: keep the side view button clickable
claude Oct 7, 2026
71d22dd
Merge #4's batch S into the side pane integration branch
claude Oct 7, 2026
949a0fd
Side pane batch T: webview preferences allow-list, no restart prompt …
claude Oct 7, 2026
c87f9ae
Merge #4's batch T into the side pane integration branch
claude Oct 7, 2026
f6e0c89
Side chat: a failed non-streaming reply save is no longer reported as…
claude Oct 7, 2026
c03abb0
Side chat: a late save cannot recreate a deleted child topic
claude Oct 7, 2026
64bd7e7
Side chat: regenerate claims the composer before its history save
claude Oct 7, 2026
4d396c7
Code viewer: keep the horizontal scrollbar in view and line numbers h…
claude Oct 7, 2026
d9e1a0e
Side chat: picking a model clears the "pick a model first" error
claude Oct 7, 2026
24f0a8f
Merge origin/claude/project-thread-l0d4fb into the side pane integrat…
claude Oct 7, 2026
edc7f0f
Merge #8's code viewer and side chat entry fixes into the side pane i…
claude Oct 7, 2026
cedb58d
Merge #3's side chat save fixes into the side pane integration branch
claude Oct 7, 2026
9b95b7d
Code viewer: drop the wrapped-gutter rule #4 and #8 both added
claude Oct 7, 2026
fe604d9
Side pane: keyboard side chat message menu, tab menu placement, silen…
claude Oct 7, 2026
37ace59
Side chat dormancy: keep removed references and picked model across s…
claude Oct 7, 2026
13bbcb4
Merge #3's side chat dormancy draft-cache fix into the side pane inte…
claude Oct 7, 2026
0d256a0
Side chat: a woken side chat returns to the message the reader left
claude Oct 7, 2026
e945181
Trajectory tab: keyboard expansion menu
claude Oct 7, 2026
ef2607a
Side pane batch U: keep the side browser in its own session, restore …
claude Oct 7, 2026
959c99b
Merge #4's batch U into the side pane integration branch
claude Oct 7, 2026
6f0ed61
Merge #10's side chat message-anchored scroll restore into the side p…
claude Oct 7, 2026
8933936
Side pane: after a restart, come back to the side chat the topic was …
claude Oct 7, 2026
5385e53
Merge origin/claude/project-thread-l0d4fb into the side pane integrat…
claude Oct 7, 2026
0cb15ff
Merge origin/claude/project-thread-p17ix8 into the side pane integrat…
claude Oct 7, 2026
b3af5ae
Side pane end-to-end smoke: real Electron, every tab kind, flat counters
claude Oct 7, 2026
cc5686e
Side pane batch V: interaction fixes from a review of the integration…
claude Oct 7, 2026
7637efb
Merge #4's batch V into the side pane integration branch
claude Oct 7, 2026
8931855
Side pane smoke CI: Electron-ABI native modules, Electron log on failure
claude Oct 7, 2026
5439cce
Side pane: follow a topic as soon as it is picked, not after its hist…
claude Oct 7, 2026
407b4c0
Merge origin/claude/project-thread-k7mpdc into the side pane integrat…
claude Oct 7, 2026
f94a92d
Merge origin/claude/project-thread-p17ix8 into the side pane integrat…
claude Oct 7, 2026
5447138
Side pane smoke: let a woken side chat finish rendering before counting
claude Oct 7, 2026
f5504b9
Merge #11: side pane smoke waits for the woken side chat to render be…
claude Oct 7, 2026
f47d342
Side pane leak test: wait for queued animation frames before counting
claude Oct 7, 2026
ae26b3d
Merge #12: side pane leak test waits for queued animation frames
claude Oct 7, 2026
cc43f4d
Composer: keep the send button inside the card when the side pane nar…
claude Oct 7, 2026
c9b4371
Notifications: take down the floating copies once the side pane shows…
claude Oct 7, 2026
c825f69
Merge #8's side chat width and approval card fixes into the side pane…
claude Oct 7, 2026
359a936
Side chat: stopping against an unresponsive VCP server no longer lock…
claude Oct 7, 2026
956ba44
Side pane smoke: retry removing the temp data dir after a passing run
claude Oct 7, 2026
db8c635
Side pane: reuse tab strip nodes, skip hidden overview rebuilds, app …
claude Oct 7, 2026
6c10ac2
Merge #3's side chat stop fix into the side pane integration branch
claude Oct 7, 2026
5447546
Side pane batch W: state-machine and lazy-load failure edges
claude Oct 7, 2026
d03eb71
Merge #13: tab strip reuses nodes, overview not rebuilt while closed,…
claude Oct 7, 2026
519fa79
Merge origin/claude/project-thread-1zv9nc into the side pane integrat…
claude Oct 7, 2026
34099aa
Merge origin/claude/project-thread-p17ix8 into the side pane integrat…
claude Oct 7, 2026
e0f8a2b
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
74c44a4
Workspaces: only the main window may add, change or remove workspaces
claude Oct 7, 2026
554a55c
Side chats stay mounted again instead of sleeping
claude Oct 7, 2026
47d7f9b
Side pane dormancy test: mark the busy view before the hidden timers …
claude Oct 7, 2026
f04bac7
Side terminal: submit the workspace jump again
claude Oct 7, 2026
b249fac
Git service: line counts only where they are drawn; drop status coale…
claude Oct 7, 2026
095ee86
Message file changes: remember skipped messages by content, not length
claude Oct 7, 2026
a350463
Side browser: switching windows keeps a half-typed address
claude Oct 7, 2026
21a0a48
Side pane batch X: maintainability pass
claude Oct 7, 2026
720a8e7
Side pane batch X: test plan tab focus
claude Oct 7, 2026
e22543e
Side pane IPC: one caller-window policy enforced where channels are r…
claude Oct 7, 2026
bce85f5
Side terminal: send the first chunk after quiet at once
claude Oct 7, 2026
3958cf1
Workspace sender test: stub fileManager so it runs without optional s…
claude Oct 7, 2026
93c8ad8
Agent settings: a draft with a cleared name keeps the Agent's own name
claude Oct 7, 2026
fe07f9a
Merge #4's batch X into the side pane integration branch
claude Oct 7, 2026
a9c64c7
Merge #3's workspace and side pane IPC caller-window policy into the …
claude Oct 7, 2026
4af1a57
Merge #14: side chats stay mounted again, regressions from integratio…
claude Oct 7, 2026
5ff688f
Side pane tests: assert behavior instead of copy, drop fixed sleeps a…
claude Oct 7, 2026
ee99d90
Notifications: closed toasts no longer pile up in the window's listen…
claude Oct 7, 2026
c31ac61
Merge #15: side pane tests assert behaviour, fixed sleeps and duplica…
claude Oct 7, 2026
9bac71a
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
791bfd9
fix(side-chat): Stop keeps the reply that already streamed
claude Oct 7, 2026
1f076a1
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
e3dac07
wip: batch Y state UI fixes
claude Oct 7, 2026
5c79281
Side terminal: the restart confirm keeps focus
claude Oct 7, 2026
2ed0100
Merge remote-tracking branch 'origin/claude/project-thread-8tpfrr' in…
claude Oct 7, 2026
e7646f5
Side pane: comments and docs follow side chats staying mounted
claude Oct 7, 2026
9773d95
Side pane batch Y: loading, error and retry states
claude Oct 7, 2026
f5ef111
Merge #3: stop keeps a reply that already finished streaming
claude Oct 7, 2026
8591055
Merge #16: closed notification toasts no longer pile up in the window…
claude Oct 7, 2026
9e0b65c
Run the new notification, listener owner, side chat stop and main cha…
claude Oct 7, 2026
fa6ff4a
Side pane tests: restore three guards #15 loosened, and pin side chat…
claude Oct 7, 2026
da587cc
Merge branch 'claude/project-thread-1zv9nc' of https://github.com/Rox…
claude Oct 7, 2026
16a2694
fix(source): refuse NTFS aliases of .git and symlinks into .git
claude Oct 7, 2026
969a129
Side terminal: only the focused view sizes the shared PTY; others fol…
claude Oct 7, 2026
1930a0b
Side chat: keep the composer usable while a reply generates; Esc stop…
claude Oct 7, 2026
0c4466f
Merge #17: terminal restart confirm keeps focus, restore three regres…
claude Oct 7, 2026
7c82fc2
Tab strip drag: finish outside the strip, don't rebuild tabs mid-drag
claude Oct 7, 2026
8934087
Side pane batch Z: persistence and restore fixes
claude Oct 7, 2026
a9c1efb
Terminal: let the restart confirm dialog keep focus
claude Oct 7, 2026
00e1bf4
Side pane: in the narrow overlay layout, clicking the conversation co…
claude Oct 7, 2026
72577d5
Merge #3 16a26941: block .git aliases and symlinks in source editor, …
claude Oct 7, 2026
c593e17
Side terminal: keys typed while connecting are sent once it connects
claude Oct 7, 2026
513fcba
Side pane batch Z: keep trajectory reading position across dormancy
claude Oct 7, 2026
151a67f
Run the source service and PowerShell output boundary tests in test:w…
claude Oct 7, 2026
b1bf41b
Merge #8: side chat composer usable while generating, Esc stops; term…
claude Oct 7, 2026
2eb0892
Merge #4 batch Z: persistence and restore fixes
claude Oct 7, 2026
7614ad3
Merge #18: shared terminal size, typing before connect, tab drag, nar…
claude Oct 7, 2026
fec5ed7
Regenerate the chat event graph after merging #18
claude Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/chat_kernel_ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ on:
pull_request:
paths:
- 'renderer.js'
- 'main.js'
- 'style.css'
- 'Groupmodules/**'
- 'ProjectForgemodules/**'
- 'VCPDistributedServer/Plugin/PowerShellExecutor/**'
- 'VCPDistributedServer/Plugin/ProjectForge/**'
- 'vendor/xterm/**'
- 'main.html'
- 'modules/**'
- 'preloads/**'
Expand Down Expand Up @@ -54,6 +61,12 @@ jobs:
- name: Run Chat Kernel tests
run: npm run test:chat-kernel

- name: Run side pane tests
run: npm run test:side-pane

- name: Run side pane workbench surface tests
run: npm run test:workbench

- name: Run Chat contract evidence gate
env:
VCPCHAT_PACKAGED_ARTIFACT_SMOKE_OUTPUT: ${{ runner.temp }}/packaged-artifact-smoke.json
Expand Down Expand Up @@ -91,3 +104,45 @@ jobs:

- name: Run complete UI system gate
run: npm run check:ui-system

side-pane-e2e:
name: Side pane end-to-end smoke (real Electron)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Setup Node 22
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: npm

# 和 mobile_sync 打包任务一样:先装依赖不跑原生脚本,再按 Electron 的 ABI 重编原生模块,
# 按 Node ABI 编出来的原生模块(better-sqlite3 等)在 Electron 里加载不了
- name: Install package graph without native lifecycle scripts
env:
PUPPETEER_SKIP_DOWNLOAD: 'true'
run: npm ci --ignore-scripts

- name: Install Electron runtime
run: node node_modules/electron/install.js

- name: Rebuild Electron native modules used by the app
run: npx electron-rebuild -f -w better-sqlite3,node-pty,sharp

- name: Ensure a virtual display
run: command -v xvfb-run || (sudo apt-get update && sudo apt-get install -y xvfb)

- name: Run side pane smoke
env:
SIDE_PANE_SMOKE_DIR: ${{ runner.temp }}/side-pane-smoke
run: xvfb-run -a -s "-screen 0 1600x1000x24" npm run smoke:side-pane

- name: Upload smoke evidence
if: failure()
uses: actions/upload-artifact@v4
with:
name: side-pane-smoke-evidence
path: ${{ runner.temp }}/side-pane-smoke
12 changes: 10 additions & 2 deletions Groupmodules/groupchat.js
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ async function getAgentGroups() {
if (await fs.pathExists(configPath)) {
const config = normalizeGroupModeSettings(await fs.readJson(configPath));
if (config.avatar) {
config.avatarUrl = `file://${path.join(groupPath, config.avatar)}?t=${Date.now()}`;
config.avatarUrl = await versionedGroupAvatarUrl(groupPath, config.avatar);
} else {
config.avatarUrl = null;
}
Expand All @@ -639,6 +639,14 @@ async function getAgentGroups() {
}
}

// Version the avatar URL by modification time so the agent list keeps its
// cached image until the avatar file actually changes.
async function versionedGroupAvatarUrl(groupDir, avatarFile) {
const avatarPath = path.join(groupDir, avatarFile);
const stat = await fs.stat(avatarPath).catch(() => null);
return `file://${avatarPath}?v=${stat ? Math.round(stat.mtimeMs) : Date.now()}`;
}

/**
* 获取指定 AgentGroup 的配置
* @param {string} groupId - 群组 ID
Expand All @@ -652,7 +660,7 @@ async function getAgentGroupConfig(groupId) {
if (await fs.pathExists(configPath)) {
const config = normalizeGroupModeSettings(await fs.readJson(configPath));
if (config.avatar) {
config.avatarUrl = `file://${path.join(groupDir, config.avatar)}?t=${Date.now()}`;
config.avatarUrl = await versionedGroupAvatarUrl(groupDir, config.avatar);
} else {
config.avatarUrl = null;
}
Expand Down
66 changes: 61 additions & 5 deletions Groupmodules/grouprenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ window.GroupRenderer = (() => {
const pendingGroupUserMessageIds = new Set();
let groupSettingsGeneration = 0;
let groupSettingsReady = false;
// Group settings save only on the Save button, like Agent settings. Track
// unsaved edits the same way so the form shows 未保存更改 before a switch
// throws them away.
let groupSettingsDirty = false;
let groupSettingsRevision = 0;
let groupSlotsLoadPromise = null;

function ensureGroupSlotsBridge() {
Expand Down Expand Up @@ -629,6 +634,11 @@ window.GroupRenderer = (() => {
}
groupSettingsForm.addEventListener('submit', handleSaveGroupSettings);
groupSettingsForm._eventListenerAttached = true;
if (!groupSettingsForm._dirtyListenerAttached) {
groupSettingsForm.addEventListener('input', markGroupSettingsDirty);
groupSettingsForm.addEventListener('change', markGroupSettingsDirty);
groupSettingsForm._dirtyListenerAttached = true;
}


if (deleteGroupBtn?._eventListenerAttached) {
Expand All @@ -646,6 +656,7 @@ window.GroupRenderer = (() => {
groupAvatarInput._eventListenerAttached = true;

groupSettingsReady = true;
resetGroupSettingsDirty();
updateAllGroupSectionSummaries();
}

Expand Down Expand Up @@ -722,7 +733,10 @@ window.GroupRenderer = (() => {
list: sequentialSpeakerOrderList,
agents: availableAgentsForGroup,
groupConfig,
onChanged: () => updateGroupSectionSummary('mode')
onChanged: () => {
updateGroupSectionSummary('mode');
markGroupSettingsDirty();
}
});
}

Expand All @@ -732,7 +746,10 @@ window.GroupRenderer = (() => {
membersContainer: groupMembersListDiv,
agents: availableAgentsForGroup,
groupConfig,
onChanged: () => updateGroupSectionSummary('mode')
onChanged: () => {
updateGroupSectionSummary('mode');
markGroupSettingsDirty();
}
});
}

Expand All @@ -743,7 +760,10 @@ window.GroupRenderer = (() => {
membersContainer: groupMembersListDiv,
agents: availableAgentsForGroup,
groupConfig,
onChanged: () => updateGroupSectionSummary('mode')
onChanged: () => {
updateGroupSectionSummary('mode');
markGroupSettingsDirty();
}
});
}

Expand All @@ -757,7 +777,41 @@ window.GroupRenderer = (() => {
updateGroupSectionSummary('mode');
}

function reportSettingsSaveResult(success, error = '') {
function setGroupSaveState(state) {
const indicator = groupSettingsForm?.querySelector('.form-save-state-indicator');
if (!indicator) return;
indicator.dataset.state = state;
const label = indicator.querySelector('.form-state-dot-label');
if (label) label.textContent = state === 'warning' ? '未保存更改' : (state === 'ongoing' ? '保存中...' : '已保存');
const dotHost = indicator.querySelector('.form-state-dot-host');
if (!dotHost) return;
dotHost.replaceChildren();
try {
window.VCPUIUX?.mountStateDot?.(dotHost, { state, size: 7 }, {
own: () => {},
child: () => ({ own: () => {}, listen: () => {}, dispose: () => {} }),
listen: () => {},
dispose: () => {}
});
} catch (_) {}
}

function markGroupSettingsDirty() {
if (!groupSettingsReady) return;
groupSettingsDirty = true;
++groupSettingsRevision;
setGroupSaveState('warning');
}

function resetGroupSettingsDirty() {
groupSettingsDirty = false;
groupSettingsRevision = 0;
setGroupSaveState('done');
}

function reportSettingsSaveResult(success, error = '', saveRevision = null) {
if (success && saveRevision === groupSettingsRevision) groupSettingsDirty = false;
setGroupSaveState(success && !groupSettingsDirty ? 'done' : 'warning');
groupSettingsForm?.dispatchEvent(new CustomEvent('vcp-settings-save-result', {
detail: { success: Boolean(success), error }
}));
Expand Down Expand Up @@ -790,6 +844,8 @@ window.GroupRenderer = (() => {
const selectedMemberIds = [...getSelectedMemberIds()];
const targetGroupId = groupId;
const saveGeneration = groupSettingsGeneration;
const saveRevision = groupSettingsRevision;
setGroupSaveState('ongoing');
const saveSurface = window.VCPSettingsSidebar;
const saveSnapshot = saveSurface?.getSnapshot?.();
const isCurrentSave = () => {
Expand Down Expand Up @@ -959,7 +1015,7 @@ window.GroupRenderer = (() => {
const saveButton = groupSettingsForm.querySelector('button[type="submit"]');

if (result.success && result.agentGroup) {
reportSettingsSaveResult(true);
reportSettingsSaveResult(true, '', saveRevision);
if (saveButton) uiHelper.showSaveFeedback(saveButton, true, "已保存!", "保存群组设置");
await mainRendererFunctions.loadItems(); // Reload list to reflect name/avatar changes
if (!isCurrentSave()) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,15 @@ ipcMain.handle('read-from-clipboard', () => {
function applyPtyResize(cols, rows) {
const normalizedCols = Number(cols);
const normalizedRows = Number(rows);
const previous = lastKnownSize;

if (Number.isInteger(normalizedCols) && Number.isInteger(normalizedRows) && normalizedCols > 0 && normalizedRows > 0) {
lastKnownSize = { cols: normalizedCols, rows: normalizedRows };
}
// 镜像视图(侧栏终端)里不持有尺寸的那些要跟着新尺寸画
if (lastKnownSize.cols !== previous.cols || lastKnownSize.rows !== previous.rows) {
notifyMirrors('onResize', lastKnownSize.cols, lastKnownSize.rows);
}

if (ptyProcess) {
try {
Expand Down Expand Up @@ -742,20 +747,50 @@ function dispatchPtyData(rawData) {
// 输入与尺寸调整直接落到同一个 PTY,AI 工具执行的命令因此对两处都可见。
const MIRROR_REPLAY_LIMIT = 256 * 1024;
const mirrorSinks = new Set();
let replayBuffer = '';
// 回放缓存按块存:满了从头部整块丢掉,每块均摊 O(1),只在挂载回放时拼一次。
// 原来每块都把 256KB 拼接再切片,刷屏输出时主进程每秒要复制几百 MB(同 DSH terminal-io 的有界缓冲预算)
let replayChunks = [];
let replayHead = 0;
let replayLength = 0;
let mirrorStartupPending = false;
let mirrorStartupHeld = '';

function emitMirrorData(dataStr) {
if (!dataStr) return;
// 侧栏终端可能在会话进行中才挂载,缓存最近输出用于回放。
replayBuffer += dataStr;
if (replayBuffer.length > MIRROR_REPLAY_LIMIT) {
replayBuffer = replayBuffer.slice(-MIRROR_REPLAY_LIMIT);
}
appendReplay(dataStr);
notifyMirrors('onData', dataStr);
}

function appendReplay(dataStr) {
replayChunks.push(dataStr);
replayLength += dataStr.length;
while (replayLength - replayChunks[replayHead].length >= MIRROR_REPLAY_LIMIT) {
replayLength -= replayChunks[replayHead].length;
replayChunks[replayHead++] = undefined;
}
if (replayHead > 1024 && replayHead * 2 > replayChunks.length) {
replayChunks = replayChunks.slice(replayHead);
replayHead = 0;
}
}

function readReplay() {
if (replayLength === 0) return '';
const text = replayChunks.slice(replayHead).join('');
if (text.length <= MIRROR_REPLAY_LIMIT) return text;
// 超出的部分从下一行开头回放,不从控制序列中间切开
const kept = text.slice(-MIRROR_REPLAY_LIMIT);
const lineStart = kept.indexOf('\n');
return lineStart >= 0 && lineStart < 4096 ? kept.slice(lineStart + 1) : kept;
}

function clearReplay() {
replayChunks = [];
replayHead = 0;
replayLength = 0;
}

/** 结束启动握手:成功时只放出就绪标记之后的内容(通常是提示符),失败时原样放出便于排查。 */
function releaseMirrorStartup(afterReady = null) {
if (!mirrorStartupPending) return;
Expand All @@ -782,9 +817,10 @@ function notifyMirrors(method, ...args) {
*/
function attachMirror(sink) {
mirrorSinks.add(sink);
if (replayBuffer) {
const replay = readReplay();
if (replay) {
try {
sink.onData(replayBuffer);
sink.onData(replay);
} catch (e) {
console.error('[PowerShellExecutor] Mirror replay failed:', e);
}
Expand Down Expand Up @@ -886,7 +922,7 @@ function createNewPtySession() {
guiWindow.webContents.send('powershell-clear');
}
}
replayBuffer = '';
clearReplay();
notifyMirrors('onClear');

let shell = 'bash';
Expand Down Expand Up @@ -1005,6 +1041,14 @@ function createNewPtySession() {
reject(new Error('PowerShell did not complete its startup readiness probe within 15 seconds.'));
}, 15000);

// macOS / Linux 起的是 bash:PowerShell 的初始化写进去只会报语法错误,边界永远等不到,
// 前 15 秒侧栏一片空白。环境变量 spawn 时已经给了,这里只打印边界:两段引号拼起来,
// 回显的命令行里是 '…''…',不会提前命中
if (os.platform() !== 'win32') {
const split = Math.floor(readyBoundary.length / 2);
currentPtyProcess.write(`printf '%s\\n' '${readyBoundary.slice(0, split)}''${readyBoundary.slice(split)}'; clear\r`);
return;
}
const initializationCommand = [
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
'$env:PAGER = "cat"',
Expand All @@ -1024,6 +1068,8 @@ function createNewPtySession() {
].join('; ');
currentPtyProcess.write(`${initializationCommand}\r`);
});
// 只有 AI 命令会等它;没人等的时候超时也不能变成未处理的 rejection
ptyReadyPromise.catch(() => {});

// 当 pty 进程意外退出时,清理资源。
// 注意:newSession 会先 kill 旧 PTY 再创建新 PTY,旧 PTY 的异步 onExit 不能误清理新会话。
Expand Down Expand Up @@ -1917,7 +1963,7 @@ function cleanup() {

// 4. 确保 ptyProcess 状态被重置
mirrorSinks.clear();
replayBuffer = '';
clearReplay();
mirrorStartupPending = false;
mirrorStartupHeld = '';
ptyProcess = null;
Expand Down
15 changes: 13 additions & 2 deletions VCPDistributedServer/Plugin/PowerShellExecutor/commandRunStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,24 @@ function listCommandRuns() {
return commandRuns.map(summarizeCommandRun).reverse();
}

// 清洗是逐字符的屏幕模拟,几百 KB 要几十毫秒,而且在主进程里同步跑:输出没变时直接用上次的结果
const cleanedOutput = new WeakMap(); // run -> { raw, clean }

function cleanRunOutput(run) {
const cached = cleanedOutput.get(run);
if (cached && cached.raw === run.raw) return cached.clean;
// 起始标记那一行留下的换行不算输出
const clean = sanitizeTerminalOutput(run.raw).replace(/\r\n/g, '\n').replace(/\r/g, '').replace(/^\n/, '');
cleanedOutput.set(run, { raw: run.raw, clean });
return clean;
}

/** 读取一条记录,输出为清洗后的纯文本,只保留末尾 maxChars 个字符。 */
function getCommandRun(id, { maxChars = COMMAND_RUN_READ_LIMIT } = {}) {
const run = commandRuns.find(item => item.id === id);
if (!run) return null;
const limit = Math.max(1, Math.min(COMMAND_RUN_READ_LIMIT, Math.floor(Number(maxChars)) || COMMAND_RUN_READ_LIMIT));
// 起始标记那一行留下的换行不算输出
const clean = sanitizeTerminalOutput(run.raw).replace(/\r\n/g, '\n').replace(/\r/g, '').replace(/^\n/, '');
const clean = cleanRunOutput(run);
const truncated = run.rawTruncated || clean.length > limit;
return {
...summarizeCommandRun(run),
Expand Down
Loading
Loading