Merge remote-tracking branch 'origin/master' into feat/claude-voice-integration

# Conflicts:
#	CLAUDE.md
This commit is contained in:
Codeman maintainer
2026-08-10 12:56:40 +02:00
21 changed files with 2506 additions and 782 deletions
+89 -4
View File
@@ -504,8 +504,11 @@ Object.assign(CodemanApp.prototype, {
this._syncSettingsChips();
this._syncModelCards();
this._syncEffortSegment();
// Back to the top of the document (one scroll, not a tab reset).
this.switchSettingsTab('settings-terminal');
// Back to the top of the document (one scroll, not a tab reset). Updates is
// first now: the version this install is running, and whether a newer one is
// waiting, are the two things worth seeing before any preference. The rest of
// the system settings (paths, automation, remote access) tail the document.
this.switchSettingsTab('settings-updates');
const modal = document.getElementById('appSettingsModal');
modal.classList.add('active');
@@ -688,11 +691,89 @@ Object.assign(CodemanApp.prototype, {
document.getElementById('appSettingsJump')?.setAttribute('aria-expanded', open ? 'true' : 'false');
},
/** Mirror checkbox state onto the chip labels (see _initSettingsNav). */
/**
* Mirror checkbox state onto the chip labels (see _initSettingsNav).
*
* Covers Session Options too: it shares the `set-*` surface, and its cycle-step
* chips would otherwise depend on `:has()` alone for their checked styling.
*/
_syncSettingsChips() {
document.querySelectorAll('#appSettingsModal .set-chip').forEach(chip => {
document.querySelectorAll('#appSettingsModal .set-chip, #sessionOptionsModal .set-chip').forEach(chip => {
chip.classList.toggle('is-on', !!chip.querySelector('input')?.checked);
});
this._syncLayoutPreview();
},
/**
* Redraw the Header & Panels live preview from the chips above it.
*
* The preview is a scale model of the app, not a second list of settings, so
* every icon is CLONED from the chip that owns it (`.set-chip-ico`): each icon
* has exactly ONE copy in index.html and a chip can never drift from the button
* it previews. A chip joins the preview purely by carrying `data-preview`
* (which slot) and `data-preview-order` (where in that slot); nothing here
* needs to know the setting's name.
*
* `data-preview-text` replaces the icon with a text token for the header
* entries that are readouts rather than buttons (plan usage, CPU, font size).
*/
_syncLayoutPreview() {
const modal = document.getElementById('appSettingsModal');
if (!modal || typeof modal.querySelectorAll !== 'function') return;
const slots = {
header: document.getElementById('appSettingsPreviewHeader'),
panel: document.getElementById('appSettingsPreviewPanels'),
toolbar: document.getElementById('appSettingsPreviewToolbar'),
float: document.getElementById('appSettingsPreviewFloats'),
};
if (!slots.header) return;
Object.values(slots).forEach(el => {
if (el) el.innerHTML = '';
});
const chips = [...modal.querySelectorAll('.set-chip[data-preview]')]
.filter(chip => chip.querySelector('input')?.checked)
.sort((a, b) => (Number(a.dataset.previewOrder) || 0) - (Number(b.dataset.previewOrder) || 0));
let shown = 0;
for (const chip of chips) {
const kind = chip.dataset.preview;
const slot = slots[kind];
if (!slot) continue;
// The label is the chip's own text; the icon span (if any) is skipped by
// taking the LAST span, which is always the label.
const spans = chip.querySelectorAll('span');
const label = (spans[spans.length - 1]?.textContent || '').trim();
const el = document.createElement('span');
el.title = label;
if (kind === 'header') {
const text = chip.dataset.previewText;
el.className = text ? 'set-preview-chip' : 'set-preview-btn';
if (text) el.textContent = text;
else this._appendPreviewIcon(el, chip);
} else {
el.className = `set-preview-${kind}`;
this._appendPreviewIcon(el, chip);
const name = document.createElement('span');
name.textContent = label;
el.appendChild(name);
}
slot.appendChild(el);
shown++;
}
const empty = document.getElementById('appSettingsPreviewEmpty');
if (empty) empty.hidden = shown > 0;
},
/** Clone a chip's icon into a preview element (see _syncLayoutPreview). */
_appendPreviewIcon(target, chip) {
const icon = chip.querySelector('.set-chip-ico');
if (!icon) return;
const clone = icon.cloneNode(true);
clone.classList.remove('set-chip-ico');
clone.classList.add('set-preview-ico');
target.appendChild(clone);
},
/**
@@ -861,6 +942,10 @@ Object.assign(CodemanApp.prototype, {
section.classList.toggle('set-hit-hidden', !!q && !hasVisible);
});
// The live preview sits outside any group, so it survives the sweep above;
// a search is asking for one row, not for the scale model around it.
doc.querySelectorAll('.set-preview').forEach(pv => pv.classList.toggle('set-hit-hidden', !!q));
const empty = document.getElementById('appSettingsSearchEmpty');
if (empty) empty.hidden = !q || anyVisible;
if (!q) doc.querySelectorAll('.set-group-advanced').forEach(g => g.classList.remove('open'));