From 702fc7c3d45c9cc11739c4dc422cf5d66bc11cd4 Mon Sep 17 00:00:00 2001 From: Matt Flax Date: Thu, 19 Mar 2026 17:17:14 +1100 Subject: [PATCH 01/29] feat: add SVG to text diff mode toggle Add bidirectional mode switching between visual SVG editor and text diff viewer. Users can now toggle between editors via dedicated UI buttons while preserving file state and dirty status across switches. - Add toggle-svg-mode event handler in app shell - Implement _onToggleSvgMode to manage viewer visibility and file sync - Add visual mode button to diff viewer for .svg files - Add code button to SVG viewer for text diff mode - Preserve savedContent through mode switches for dirty state tracking - Update AWS region config ap-northeast-1 to ap-northeast-3 - Clean up verbose console logs and improve styling --- src/ac_dc/config/llm.json | 2 +- tests/sample.svg | 4 +- webapp/src/app-shell.js | 93 ++++++++++++++++++++++++++++ webapp/src/components/diff-viewer.js | 55 +++++++++++++++- webapp/src/components/svg-viewer.js | 41 ++++++++---- 5 files changed, 180 insertions(+), 15 deletions(-) diff --git a/src/ac_dc/config/llm.json b/src/ac_dc/config/llm.json index 495a99bc..23a0bf43 100644 --- a/src/ac_dc/config/llm.json +++ b/src/ac_dc/config/llm.json @@ -1,6 +1,6 @@ { "env": { - "AWS_REGION": "ap-northeast-1" + "AWS_REGION": "ap-northeast-3" }, "model": "bedrock/global.anthropic.claude-opus-4-6-v1", "smallerModel": "bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0" diff --git a/tests/sample.svg b/tests/sample.svg index ac8cca07..f057be28 100644 --- a/tests/sample.svg +++ b/tests/sample.svg @@ -1,4 +1,4 @@ - + @@ -34,7 +34,7 @@ - + AC⚑DC Architecture diff --git a/webapp/src/app-shell.js b/webapp/src/app-shell.js index b982bbda..329a7a46 100644 --- a/webapp/src/app-shell.js +++ b/webapp/src/app-shell.js @@ -350,8 +350,10 @@ class AcApp extends JRPCClient { this._onToastEvent = this._onToastEvent.bind(this); this._onActiveFileChanged = this._onActiveFileChanged.bind(this); this._onLoadDiffPanel = this._onLoadDiffPanel.bind(this); + this._onToggleSvgMode = this._onToggleSvgMode.bind(this); this._onBeforeUnload = this._onBeforeUnload.bind(this); this._onWindowResize = this._onWindowResize.bind(this); + this._svgModeOverride = false; this._resizeRAF = null; } @@ -368,6 +370,7 @@ class AcApp extends JRPCClient { window.addEventListener('search-navigate', this._onSearchNavigate); window.addEventListener('active-file-changed', this._onActiveFileChanged); window.addEventListener('load-diff-panel', this._onLoadDiffPanel); + window.addEventListener('toggle-svg-mode', this._onToggleSvgMode); // Intercept Ctrl+S globally and Alt+Arrow for file navigation window.addEventListener('keydown', this._onGlobalKeyDown, true); @@ -396,6 +399,7 @@ class AcApp extends JRPCClient { window.removeEventListener('search-navigate', this._onSearchNavigate); window.removeEventListener('active-file-changed', this._onActiveFileChanged); window.removeEventListener('load-diff-panel', this._onLoadDiffPanel); + window.removeEventListener('toggle-svg-mode', this._onToggleSvgMode); window.removeEventListener('keydown', this._onGlobalKeyDown, true); window.removeEventListener('keyup', this._onGlobalKeyUp); window.removeEventListener('ac-toast', this._onToastEvent); @@ -1103,6 +1107,7 @@ class AcApp extends JRPCClient { * When either viewer switches active file, activate the correct viewer layer. */ _onActiveFileChanged(e) { + if (this._svgModeOverride) return; const path = e.detail?.path; if (path) { const isSvg = path.toLowerCase().endsWith('.svg'); @@ -1202,6 +1207,94 @@ class AcApp extends JRPCClient { } } + /** + * Toggle between SVG visual editor and text diff editor for .svg files. + */ + _onToggleSvgMode(e) { + const { path, target, modified: textModified, savedContent: incomingSaved } = e.detail || {}; + if (!path) return; + + const diffV = this.shadowRoot?.querySelector('ac-diff-viewer'); + const svgV = this.shadowRoot?.querySelector('ac-svg-viewer'); + + this._svgModeOverride = true; + + if (target === 'diff') { + // Capture latest SVG content before switching + if (svgV) svgV._captureEditorContent?.(); + const svgFile = svgV?._files?.find(f => f.path === path); + const original = svgFile?.original || ''; + const modified = svgFile?.modified || ''; + + // Flip visibility + if (diffV) { + diffV.classList.add('viewer-visible'); + diffV.classList.remove('viewer-hidden'); + } + if (svgV) { + svgV.classList.add('viewer-hidden'); + svgV.classList.remove('viewer-visible'); + } + + // Close any stale tab then open fresh with SVG content. + // Set savedContent to the on-disk original so that visual edits + // from the SVG editor appear as dirty (unsaved) in the diff viewer. + if (diffV) { + diffV.closeFile(path); + const effectiveModified = modified || original; + diffV.openFile({ + path, + original, + modified: effectiveModified, + is_new: svgFile?.is_new ?? false, + is_read_only: false, + savedContent: incomingSaved ?? svgFile?.savedContent ?? original, + }); + requestAnimationFrame(() => { + if (diffV._editor) diffV._editor.layout(); + this._svgModeOverride = false; + }); + } else { + this._svgModeOverride = false; + } + + } else if (target === 'visual') { + // Read SVG file data BEFORE closing (closeFile removes it from _files) + const svgFile = svgV?._files?.find(f => f.path === path); + const original = svgFile?.original ?? ''; + + // Flip visibility + if (svgV) { + svgV.classList.add('viewer-visible'); + svgV.classList.remove('viewer-hidden'); + } + if (diffV) { + diffV.classList.add('viewer-hidden'); + diffV.classList.remove('viewer-visible'); + } + + // Pass text edits back to SVG viewer β€” close and reopen with + // the latest content from the diff editor. Carry savedContent + // through so dirty state is preserved across mode toggles. + if (svgV) { + const savedContent = incomingSaved ?? svgFile?.savedContent ?? original; + svgV.closeFile(path); + svgV.openFile({ + path, + original, + modified: textModified || original, + is_new: svgFile?.is_new ?? false, + savedContent, + }); + } + if (diffV) diffV.closeFile(path); + + requestAnimationFrame(() => { this._svgModeOverride = false; }); + } else { + this._svgModeOverride = false; + } + } + _onBeforeUnload() { this._saveViewportState(); } diff --git a/webapp/src/components/diff-viewer.js b/webapp/src/components/diff-viewer.js index 0fbfa2cc..0c88f247 100644 --- a/webapp/src/components/diff-viewer.js +++ b/webapp/src/components/diff-viewer.js @@ -212,6 +212,29 @@ export class AcDiffViewer extends RpcMixin(LitElement) { border-radius: 2px; } + .visual-btn { + position: absolute; + top: 6px; + right: 64px; + z-index: 10; + display: flex; + align-items: center; + gap: 5px; + padding: 3px 10px; + border: 1px solid var(--border, #444); + border-radius: 4px; + background: var(--bg-secondary, #1e1e1e); + color: var(--text-muted, #999); + font-size: 0.75rem; + cursor: pointer; + transition: background 0.15s, color 0.15s, border-color 0.15s; + } + .visual-btn:hover { + background: var(--bg-tertiary, #2a2a2a); + color: var(--text-primary, #e0e0e0); + border-color: var(--text-muted, #666); + } + /* Split layout for preview mode */ .split-container { display: flex; @@ -499,7 +522,7 @@ export class AcDiffViewer extends RpcMixin(LitElement) { is_config: opts.is_config ?? false, config_type: opts.config_type ?? null, real_path: opts.real_path ?? null, - savedContent: modified, + savedContent: opts.savedContent ?? modified, }; this._files = [...this._files, fileObj]; @@ -1093,6 +1116,7 @@ export class AcDiffViewer extends RpcMixin(LitElement) { configType: file.config_type, }, })); + } /** @@ -1486,6 +1510,27 @@ export class AcDiffViewer extends RpcMixin(LitElement) { return ext === '.md' || ext === '.markdown'; } + _isSvgFile(path) { + if (!path) return false; + return path.toLowerCase().endsWith('.svg'); + } + + _switchToVisualMode() { + if (this._activeIndex < 0) return; + const file = this._files[this._activeIndex]; + if (!file) return; + // Capture latest text content from Monaco + const modified = this._editor?.getModifiedEditor()?.getValue() ?? file.modified; + window.dispatchEvent(new CustomEvent('toggle-svg-mode', { + detail: { + path: file.path, + target: 'visual', + modified, + savedContent: file.savedContent, + }, + })); + } + _togglePreview() { this._previewMode = !this._previewMode; if (this._previewMode) { @@ -1747,7 +1792,15 @@ export class AcDiffViewer extends RpcMixin(LitElement) { _renderOverlayButtons(file, isDirty, showPreviewBtn) { if (!file) return nothing; + const showVisualBtn = this._isSvgFile(file.path); return html` + ${showVisualBtn ? html` + + ` : nothing} ${showPreviewBtn ? html`
+ + + + + - -
+ +
+ + ${this._searchMode === 'files' ? html` +
+ ${this._fileSearchLoading ? html` +
Searching...
+ ` : !this._chatSearchQuery.trim() ? html` +
Type to search across files
+ ` : this._fileSearchResults.length === 0 ? html` +
No results found
+ ` : this._fileSearchResults.map(r => this._renderFileSearchSection(r))} +
+ ` : nothing} + + +
${!hasMessages ? html`
AC⚑DC
@@ -2926,6 +3514,7 @@ export class AcChatPanel extends RpcMixin(LitElement) {
+
- - -
-
- - ${this._loading ? html` -
Searching...
- ` : !hasResults ? html` -
- ${this._query.trim() ? 'No results found' : 'Type to search across files'} -
- ` : html` -
- -
-
- ${this._results.map(r => this._renderFileSection(r))} -
-
- `} - `; - } -} - -customElements.define('ac-search-tab', AcSearchTab); \ No newline at end of file From 017baa197b4c4940de90a16c09d42a05e7c8d3e7 Mon Sep 17 00:00:00 2001 From: Matt Flax Date: Fri, 20 Mar 2026 15:57:10 +1100 Subject: [PATCH 17/29] feat: move git actions to dialog header Reorganize git action buttons (copy diff, commit, reset) from chat panel action bar to dialog header's right-side actions area. Add header divider and action dividers in search controls. Update search UI with inline toggles inside input border (VS Code pattern). Move session buttons to separate group in action bar. Add commit state tracking with spinning animation and disable during review/ streaming. Update specs to reflect new layout and delegation flow. --- specs3/5-webapp/app_shell_and_dialog.md | 4 +- specs3/5-webapp/chat_interface.md | 24 ++-- specs3/5-webapp/search_and_settings.md | 12 +- webapp/src/components/ac-dialog.js | 61 +++++++++ webapp/src/components/ac-files-tab.js | 26 ++++ webapp/src/components/chat-panel.js | 166 +++++++++++++----------- 6 files changed, 199 insertions(+), 94 deletions(-) diff --git a/specs3/5-webapp/app_shell_and_dialog.md b/specs3/5-webapp/app_shell_and_dialog.md index 0d0a6e83..af9f0761 100644 --- a/specs3/5-webapp/app_shell_and_dialog.md +++ b/specs3/5-webapp/app_shell_and_dialog.md @@ -167,7 +167,7 @@ All handles show an accent-colored highlight on hover. All three are hidden when |---------|---------| | Left | Active tab label; click toggles minimize | | Center | Tab icon buttons | -| Right | Cross-ref toggle, review toggle (πŸ‘οΈ), minimize button | +| Right | Cross-ref toggle, mode toggle, git actions (πŸ“‹ πŸ’Ύ ⚠️), review toggle (πŸ‘οΈ), minimize button | **Cross-reference toggle:** A checkbox labeled **+doc index** (in code mode) or **+code symbols** (in document mode) appears in the header actions area, to the left of the review toggle. The checkbox is **always visible** once the initial startup completes β€” in code mode the doc index's structural extraction finishes within ~250ms of the "ready" signal (before any user interaction is possible), so the toggle is available immediately; in document mode the symbol index is always available. Checking the box calls `LLMService.set_cross_reference(true)` via RPC; unchecking calls `set_cross_reference(false)`. A toast notifies the user of the token impact on activation and confirms removal on deactivation. The checkbox resets to unchecked on mode switch. @@ -182,7 +182,7 @@ The dialog tracks review state via `_reviewActive` property, synced from: - `review-started` window event β†’ sets `true` - `review-ended` window event β†’ sets `false` -**Note:** Git action buttons (clipboard, commit, reset) and session buttons (new session, history browser) are in the chat panel's action bar, not the dialog header. See [Chat Interface β€” Action Bar](chat_interface.md#action-bar). +**Git action buttons** (πŸ“‹ copy diff, πŸ’Ύ commit, ⚠️ reset) are in the dialog header's right-side actions area, separated from the mode/review controls by a thin vertical divider (`.header-divider`). The commit button shows a spinning ⏳ while committing and is disabled during review mode or active streaming. The reset button shows a confirmation dialog via the chat panel. These buttons delegate to `ac-files-tab` β†’ `ac-chat-panel` methods where the commit/reset logic lives. **Session buttons** (✨ new session, πŸ“œ history browser) remain in the chat panel's action bar. See [Chat Interface β€” Action Bar](chat_interface.md#action-bar). ### Minimizing diff --git a/specs3/5-webapp/chat_interface.md b/specs3/5-webapp/chat_interface.md index b0568f9c..502d24d5 100644 --- a/specs3/5-webapp/chat_interface.md +++ b/specs3/5-webapp/chat_interface.md @@ -381,18 +381,20 @@ Toggleable quick-insert buttons from config. Click inserts at cursor. Drawer ope ## Action Bar -| Side | Element | Action | -|------|---------|--------| -| Left | ✨ | New session (dispatches `session-loaded` to refresh history bar) | -| Left | πŸ“œ | Browse history | -| Left | `Aa` `.*` `ab` | Search option toggles (always visible, affect both message and file search) | -| Center | πŸ’¬/πŸ“ toggle | Switch between message search and file search mode | -| Center | Search input | Searches messages (πŸ’¬ mode) or repository files (πŸ“ mode) | -| Center | Result counter + β–²/β–Ό | Navigate search results in either mode | +The action bar is divided into two visual groups by a thin vertical divider (`.action-divider`): + +| Group | Elements | Purpose | +|-------|----------|---------| +| Session | ✨ πŸ“œ | New session, browse history | +| Search | πŸ’¬/πŸ“ toggle, search input with inline Aa/.*/ab toggles, result counter + β–²/β–Ό | Unified search area | + +Git actions (πŸ“‹ πŸ’Ύ ⚠️) are in the dialog header β€” see [App Shell and Dialog β€” Header Sections](../5-webapp/app_shell_and_dialog.md#header-sections). + +The search input and its inline toggle buttons share a single border (`.chat-search-box` wrapper). The three toggles (`Aa` ignore case, `.*` regex, `ab` whole word) sit inside the input's right edge, following the VS Code pattern. Focus-within highlights the shared border. ### Chat Search (Dual Mode) -The action bar contains a unified search area that supports two modes via the πŸ’¬/πŸ“ toggle button. Three search option toggles (`Aa` ignore case, `.*` regex, `ab` whole word) are always visible and affect both modes. See [Search and Settings β€” Integrated File Search](search_and_settings.md#integrated-file-search) for the full file search specification. +The search area supports two modes via the πŸ’¬/πŸ“ toggle button (left of the input). See [Search and Settings β€” Integrated File Search](search_and_settings.md#integrated-file-search) for the full file search specification. **Message search (πŸ’¬ β€” default):** @@ -427,10 +429,6 @@ When file search mode is active, the messages area is hidden (`display: none`) a The overlay renders file match sections with `data-file-section` attributes for scroll sync targeting. Match text is highlighted using `unsafeHTML` with regex-built highlight spans. -| Right | πŸ“‹ | Copy diff to clipboard | -| Right | πŸ’Ύ | Commit all (server-driven β€” see below) | -| Right | ⚠️ | Reset to HEAD (with confirmation). Calls `LLMService.reset_to_head` which resets git and records a system event message in conversation context. On success, adds a system event card to the chat: "**Reset to HEAD** β€” all uncommitted changes have been discarded." | - ### Review Status Bar When review mode is active, a slim status bar appears above the chat input showing review summary and diff inclusion count. See [Code Review β€” Review Status Bar](../4-features/code_review.md#review-status-bar). The commit button is disabled during review. diff --git a/specs3/5-webapp/search_and_settings.md b/specs3/5-webapp/search_and_settings.md index 43b2cf9b..f501d335 100644 --- a/specs3/5-webapp/search_and_settings.md +++ b/specs3/5-webapp/search_and_settings.md @@ -10,19 +10,19 @@ File search shares the action bar with chat message search in the Files tab. A m ### Action Bar Search Controls -The chat panel action bar always shows three search option toggles alongside the search input: +The search area is visually separated from adjacent button groups by thin dividers. The search input contains inline toggle buttons on the right side (inside the input border), following the VS Code pattern: | Element | Behavior | |---------|----------| -| `Aa` toggle | Ignore case (default: on) β€” affects both search modes | -| `.*` toggle | Regex mode (default: off) β€” affects both search modes | -| `ab` toggle | Whole word (default: off) β€” affects both search modes | -| πŸ’¬/πŸ“ mode toggle | Switch between message search and file search | +| πŸ’¬/πŸ“ mode toggle | Switch between message search and file search (left of input) | | Search input | Placeholder changes with mode; debounced (300ms) for file search | +| `Aa` inline toggle | Ignore case (default: on) β€” inside input, right side | +| `.*` inline toggle | Regex mode (default: off) β€” inside input, right side | +| `ab` inline toggle | Whole word (default: off) β€” inside input, right side | | Result counter | `N in M` (matches in files) for file search; `X/Y` (current/total) for message search | | β–²/β–Ό navigation | Cycle through matches in either mode | -Toggle states are persisted in `localStorage` with keys `ac-dc-search-ignore-case`, `ac-dc-search-regex`, `ac-dc-search-whole-word`. +The input and its inline toggles share a single border (`.chat-search-box` wrapper). Focus-within highlights the border. Toggle states are persisted in `localStorage` with keys `ac-dc-search-ignore-case`, `ac-dc-search-regex`, `ac-dc-search-whole-word`. ### File Search Mode diff --git a/webapp/src/components/ac-dialog.js b/webapp/src/components/ac-dialog.js index 15d3da1e..ede5c615 100644 --- a/webapp/src/components/ac-dialog.js +++ b/webapp/src/components/ac-dialog.js @@ -50,6 +50,8 @@ export class AcDialog extends RpcMixin(LitElement) { _shareUrl: { type: String, state: true }, _shareCopied: { type: Boolean, state: true }, _collabDisabled: { type: Boolean, state: true }, + _committing: { type: Boolean, state: true }, + _streamingActive: { type: Boolean, state: true }, }; static styles = [theme, scrollbarStyles, css` @@ -144,9 +146,27 @@ export class AcDialog extends RpcMixin(LitElement) { animation: pulse-building 1.5s ease-in-out infinite; cursor: wait; } + .header-action.committing { + color: var(--accent-primary); + animation: spin 1s linear infinite; + } + .header-action.danger:hover { + color: var(--accent-red); + } .header-action:disabled { pointer-events: none; } + @keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + .header-divider { + width: 1px; + height: 20px; + background: var(--border-primary); + opacity: 0.6; + flex-shrink: 0; + } @keyframes pulse-building { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.9; } @@ -611,6 +631,9 @@ export class AcDialog extends RpcMixin(LitElement) { window.addEventListener('session-loaded', () => this._refreshHistoryBar()); window.addEventListener('review-started', () => { this._reviewActive = true; }); window.addEventListener('review-ended', () => { this._reviewActive = false; }); + window.addEventListener('stream-chunk', () => { this._streamingActive = true; }); + window.addEventListener('stream-complete', () => { this._streamingActive = false; this._committing = false; }); + window.addEventListener('commit-result', () => { this._committing = false; }); window.addEventListener('mode-switch-progress', (e) => { if (this._docIndexReady) return; const { message, percent } = e.detail || {}; @@ -691,6 +714,25 @@ export class AcDialog extends RpcMixin(LitElement) { } } + // === Git action delegates === + + _getFilesTab() { + return this.shadowRoot?.querySelector('ac-files-tab'); + } + + _onCopyDiff() { + this._getFilesTab()?.copyDiff(); + } + + _onCommit() { + this._committing = true; + this._getFilesTab()?.commitAll(); + } + + _onConfirmReset() { + this._getFilesTab()?.confirmReset(); + } + _onReviewClick() { // Switch to files tab first, then open the review selector this._switchTab('files'); @@ -1290,6 +1332,25 @@ export class AcDialog extends RpcMixin(LitElement) { @click=${() => this._onModeToggle()}> ${this._docIndexBuilding ? '⏳' : this._mode === 'doc' ? 'πŸ“' : 'πŸ’»'} +
+ + ${this._canMutate ? html` + + + ` : ''} - - - +
- - ${this._canMutate ? html` - - - ` : nothing}
From 4ad2ebd32d1a19d536316a5034fe7e05d82bbc71 Mon Sep 17 00:00:00 2001 From: Matt Flax Date: Fri, 20 Mar 2026 16:00:23 +1100 Subject: [PATCH 18/29] refactor: move collab indicator to left of tab buttons Move the collaboration indicator button from the right-side header actions to the left side between the tab label and tab buttons. This positions it as a status indicator rather than an action, keeping workflow controls focused on the right. Also fix popover positioning from right to left alignment. --- specs3/5-webapp/app_shell_and_dialog.md | 4 +- webapp/src/components/ac-dialog.js | 127 ++++++++++++------------ 2 files changed, 67 insertions(+), 64 deletions(-) diff --git a/specs3/5-webapp/app_shell_and_dialog.md b/specs3/5-webapp/app_shell_and_dialog.md index af9f0761..fe76d1d1 100644 --- a/specs3/5-webapp/app_shell_and_dialog.md +++ b/specs3/5-webapp/app_shell_and_dialog.md @@ -165,10 +165,12 @@ All handles show an accent-colored highlight on hover. All three are hidden when | Section | Content | |---------|---------| -| Left | Active tab label; click toggles minimize | +| Left | Active tab label, collab indicator (πŸ‘₯); click label toggles minimize | | Center | Tab icon buttons | | Right | Cross-ref toggle, mode toggle, git actions (πŸ“‹ πŸ’Ύ ⚠️), review toggle (πŸ‘οΈ), minimize button | +**Collab indicator (πŸ‘₯):** Positioned to the left of the tab buttons (between the label and tabs), the collab button shows the connected client count when > 1. Clicking opens a popover with client details and a share URL. In single-user mode (no `--collab` flag), the popover explains how to enable collaboration. This placement treats it as a status indicator rather than an action, keeping the right-side actions area focused on workflow controls. + **Cross-reference toggle:** A checkbox labeled **+doc index** (in code mode) or **+code symbols** (in document mode) appears in the header actions area, to the left of the review toggle. The checkbox is **always visible** once the initial startup completes β€” in code mode the doc index's structural extraction finishes within ~250ms of the "ready" signal (before any user interaction is possible), so the toggle is available immediately; in document mode the symbol index is always available. Checking the box calls `LLMService.set_cross_reference(true)` via RPC; unchecking calls `set_cross_reference(false)`. A toast notifies the user of the token impact on activation and confirms removal on deactivation. The checkbox resets to unchecked on mode switch. The dialog tracks cross-ref state via `_crossRefEnabled` property, synced from: diff --git a/webapp/src/components/ac-dialog.js b/webapp/src/components/ac-dialog.js index ede5c615..202bf4d2 100644 --- a/webapp/src/components/ac-dialog.js +++ b/webapp/src/components/ac-dialog.js @@ -348,7 +348,7 @@ export class AcDialog extends RpcMixin(LitElement) { .collab-popover { position: absolute; top: 100%; - right: 0; + left: 0; margin-top: 6px; background: var(--bg-tertiary); border: 1px solid var(--border-primary); @@ -1214,6 +1214,69 @@ export class AcDialog extends RpcMixin(LitElement) {
${currentTab?.label || 'Files'} +
+ + ${this._collabPopoverOpen ? html` +
this._closeCollabPopover()}>
+
+ ${this._collabDisabled ? html` +
Collaboration Disabled
+
+ Collaboration mode is not enabled. The server is listening on localhost only. +
+
+ To allow others on your network to connect, restart with: +
+
+ ac-dc --collab +
+ ` : html` +
Connected Clients
+ ${this._collabClients.length > 0 ? html` +
    + ${this._collabClients.map(c => html` +
  • + ${c.role} + ${c.ip} + ${c.is_localhost ? html`local` : ''} +
  • + `)} +
+ ` : html` +
No clients connected
+ `} +
+
+
Share Link
+ ${this._shareUrl ? html` +
+ e.target.select()}> + +
+
+ Share this link with others on your network to collaborate. +
+ ` : html` +
+ No routable network address detected.
+ Others can connect using ws://<your-ip>:${new URLSearchParams(window.location.search).get('port') || '18080'} +
+ `} +
+ `} +
+ ` : ''} +
+
${TABS.filter(tab => !tab.conditional || this._docConvertAvailable).map(tab => html` - ${this._collabPopoverOpen ? html` -
this._closeCollabPopover()}>
-
- ${this._collabDisabled ? html` -
Collaboration Disabled
-
- Collaboration mode is not enabled. The server is listening on localhost only. -
-
- To allow others on your network to connect, restart with: -
-
- ac-dc --collab -
- ` : html` -
Connected Clients
- ${this._collabClients.length > 0 ? html` -
    - ${this._collabClients.map(c => html` -
  • - ${c.role} - ${c.ip} - ${c.is_localhost ? html`local` : ''} -
  • - `)} -
- ` : html` -
No clients connected
- `} -
-
-
Share Link
- ${this._shareUrl ? html` -
- e.target.select()}> - -
-
- Share this link with others on your network to collaborate. -
- ` : html` -
- No routable network address detected.
- Others can connect using ws://<your-ip>:${new URLSearchParams(window.location.search).get('port') || '18080'} -
- `} -
- `} -
- ` : ''} -
+
+ + ${this._canMutate ? html` + + + ` : ''} +
+ ${this._modeSwitching && !this._docIndexReady ? html`
${this._modeSwitchMessage || 'Building…'} @@ -1318,7 +1346,7 @@ export class AcDialog extends RpcMixin(LitElement) { @change=${() => this._onCrossRefToggle()} style="margin: 0; cursor: ${this._canMutate ? 'pointer' : 'not-allowed'};"> - ${this._mode === 'code' ? '+doc index' : '+code symbols'} + ${this._mode === 'code' ? '+doc' : '+code'} - ${this._canMutate ? html` - - - ` : ''} +
+
+ Share this link with others on your network to collaborate. +
+ ` : html` +
+ No routable network address detected.
+ Others can connect using ws://<your-ip>:${new URLSearchParams(window.location.search).get('port') || '18080'} +
+ `} + + `} + + `; + } + async _copyShareUrl() { if (!this._shareUrl) return; try { @@ -1221,69 +1277,6 @@ export class AcDialog extends RpcMixin(LitElement) {
${currentTab?.label || 'Files'} -
- - ${this._collabPopoverOpen ? html` -
this._closeCollabPopover()}>
-
- ${this._collabDisabled ? html` -
Collaboration Disabled
-
- Collaboration mode is not enabled. The server is listening on localhost only. -
-
- To allow others on your network to connect, restart with: -
-
- ac-dc --collab -
- ` : html` -
Connected Clients
- ${this._collabClients.length > 0 ? html` -
    - ${this._collabClients.map(c => html` -
  • - ${c.role} - ${c.ip} - ${c.is_localhost ? html`local` : ''} -
  • - `)} -
- ` : html` -
No clients connected
- `} -
-
-
Share Link
- ${this._shareUrl ? html` -
- e.target.select()}> - -
-
- Share this link with others on your network to collaborate. -
- ` : html` -
- No routable network address detected.
- Others can connect using ws://<your-ip>:${new URLSearchParams(window.location.search).get('port') || '18080'} -
- `} -
- `} -
- ` : ''} -
-
${TABS.filter(tab => !tab.conditional || this._docConvertAvailable).map(tab => html` + ${this._collabPopoverOpen ? html` +
this._closeCollabPopover()}>
+ ${this._renderCollabPopover()} + ` : ''} +
+
` : ''} +
+
${this._modeSwitching && !this._docIndexReady ? html` @@ -1361,15 +1376,6 @@ export class AcDialog extends RpcMixin(LitElement) { @click=${() => this._onModeToggle()}> ${this._docIndexBuilding ? '⏳' : this._mode === 'doc' ? 'πŸ“' : 'πŸ’»'} -
- - - -
- ` : ''} - ${this._visitedTabs.has('cache') ? html` -
- -
- ` : ''} - ${this._visitedTabs.has('settings') ? html`
From 27f593ebf4f00c3f0710a2756b0159a11dca768d Mon Sep 17 00:00:00 2001 From: Matt Flax Date: Mon, 23 Mar 2026 10:59:58 +1100 Subject: [PATCH 28/29] docs: update header layout and tab structure - Replace tab labels with icons in header - Add explicit Label column to tabs table - Reorganize header sections: move tab buttons to left, center actions group - Remove header-label styling and markup - Update git actions and review toggle placement - Clarify collab indicator positioning --- specs3/5-webapp/app_shell_and_dialog.md | 16 ++++++++-------- webapp/src/components/ac-dialog.js | 11 ----------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/specs3/5-webapp/app_shell_and_dialog.md b/specs3/5-webapp/app_shell_and_dialog.md index 48c9f793..1f461f31 100644 --- a/specs3/5-webapp/app_shell_and_dialog.md +++ b/specs3/5-webapp/app_shell_and_dialog.md @@ -84,11 +84,11 @@ Default: fixed left-docked, 50% viewport width (min 400px), full height. Right h ### Tabs -| Tab | Icon | Shortcut | -|-----|------|----------| -| FILES | πŸ“ | Alt+1 | -| CONTEXT | πŸ“Š | Alt+2 | -| SETTINGS | βš™οΈ | Alt+3 | +| Tab | Icon | Label | Shortcut | +|-----|------|-------|----------| +| files | πŸ—¨ | Chat | Alt+1 | +| context | πŸ“Š | Context | Alt+2 | +| settings | βš™οΈ | Settings | Alt+3 | The Context tab has a **Budget / Cache** pill toggle at the top. The Budget sub-view shows token allocation breakdown (system prompt, symbol map, files, URLs, history). The Cache sub-view shows cache tier blocks, stability bars, and recent changes β€” delegating rendering to an embedded `` component. The active sub-view is persisted to localStorage (`ac-dc-context-subview`). Both sub-views share the same RPC data source (`LLMService.get_context_breakdown`) and the same stale-detection / refresh-on-visible behavior. When switching to the Cache sub-view, the embedded cache tab receives an `onTabVisible()` call to ensure fresh data. @@ -165,15 +165,15 @@ All handles show an accent-colored highlight on hover. All three are hidden when | Section | Content | |---------|---------| -| Left | Active tab label; click toggles minimize | -| Center | Tab icon buttons β€” gap β€” [πŸ‘₯ | πŸ“‹πŸ’Ύβš οΈ | πŸ‘οΈ] β€” gap | +| Left | Tab icon buttons | +| Center | [πŸ‘₯ | πŸ“‹πŸ’Ύβš οΈ | πŸ‘οΈ] | | Right | Cross-ref toggle (+doc/+code), mode toggle (πŸ’»/πŸ“), doc convert (πŸ“„, conditional), minimize (β–Ό) | **Collab indicator (πŸ‘₯):** Positioned to the left of the tab buttons (between the label and tabs), the collab button shows the connected client count when > 1. Clicking opens a popover with client details and a share URL. In single-user mode (no `--collab` flag), the popover explains how to enable collaboration. This placement treats it as a status indicator rather than an action, keeping the right-side actions area focused on workflow controls. **Cross-reference toggle:** A checkbox labeled **+doc** (in code mode) or **+code** (in document mode) appears in the header actions area, to the left of the review toggle. The checkbox is **always visible** once the initial startup completes β€” in code mode the doc index's structural extraction finishes within ~250ms of the "ready" signal (before any user interaction is possible), so the toggle is available immediately; in document mode the symbol index is always available. Checking the box calls `LLMService.set_cross_reference(true)` via RPC; unchecking calls `set_cross_reference(false)`. A toast notifies the user of the token impact on activation and confirms removal on deactivation. The checkbox resets to unchecked on mode switch. -**Git action buttons** (πŸ“‹ copy diff, πŸ’Ύ commit, ⚠️ reset) are placed in a `.git-actions` group centered in the gap between the tab buttons and the right-side controls (`margin-left: auto; margin-right: auto`). This keeps frequently-used actions near the center of the header where they're easy to reach, and prevents the header from looking lopsided. The commit button shows a spinning ⏳ while committing and is disabled during review mode or active streaming. The reset button shows a confirmation dialog via the chat panel. These buttons delegate to `ac-files-tab` β†’ `ac-chat-panel` methods where the commit/reset logic lives. **Session buttons** (✨ new session, πŸ“œ history browser) remain in the chat panel's action bar. See [Chat Interface β€” Action Bar](chat_interface.md#action-bar). +**Git action buttons** (πŸ“‹ copy diff, πŸ’Ύ commit, ⚠️ reset) and the **review toggle** (πŸ‘οΈ) are placed in a `.git-actions` group centered in the gap between the tab buttons and the right-side controls (`margin-left: auto; margin-right: auto`). This keeps frequently-used actions near the center of the header where they're easy to reach, and prevents the header from looking lopsided. The commit button shows a spinning ⏳ while committing and is disabled during review mode or active streaming. The reset button shows a confirmation dialog via the chat panel. These buttons delegate to `ac-files-tab` β†’ `ac-chat-panel` methods where the commit/reset logic lives. The review toggle highlights with `accent-primary` when review is active; clicking it opens the review selector or exits review mode. **Session buttons** (✨ new session, πŸ“œ history browser) remain in the chat panel's action bar. See [Chat Interface β€” Action Bar](chat_interface.md#action-bar). The dialog tracks cross-ref state via `_crossRefEnabled` property, synced from: - `onRpcReady` / `state-loaded`: reads `cross_ref_enabled` from `get_current_state()` diff --git a/webapp/src/components/ac-dialog.js b/webapp/src/components/ac-dialog.js index 1f206131..bc974257 100644 --- a/webapp/src/components/ac-dialog.js +++ b/webapp/src/components/ac-dialog.js @@ -82,15 +82,6 @@ export class AcDialog extends RpcMixin(LitElement) { cursor: grabbing; } - .header-label { - font-size: 0.8rem; - font-weight: 600; - color: var(--text-secondary); - margin-right: 12px; - white-space: nowrap; - cursor: pointer; - } - .tab-buttons { display: flex; gap: 2px; @@ -1275,8 +1266,6 @@ export class AcDialog extends RpcMixin(LitElement) { return html`
- ${currentTab?.label || 'Files'} -
${TABS.filter(tab => !tab.hidden && (!tab.conditional || this._docConvertAvailable)).map(tab => html`