fix(app): align Settings controls and version metadata

The editor select referenced an undefined style class, and the renderer hard-coded 0.1.0 instead of reading Electron app metadata.
This commit is contained in:
tommy0103
2026-08-03 21:34:14 +08:00
committed by tommy0103
parent cb191cfad4
commit 23adfa3847
5 changed files with 77 additions and 5 deletions
+1
View File
@@ -885,6 +885,7 @@ ipcMain.handle('settings:get', () => {
const connected = sources.some((source) => source.status !== 'error'); const connected = sources.some((source) => source.status !== 'error');
return { return {
version: app.getVersion(),
providerRoots, providerRoots,
claudeDir, claudeDir,
codexDir, codexDir,
+17 -3
View File
@@ -11,7 +11,7 @@ const editorScheme = ref('vscode');
const editorSchemes = ['vscode', 'vscode-insiders', 'cursor', 'windsurf', 'zed']; const editorSchemes = ['vscode', 'vscode-insiders', 'cursor', 'windsurf', 'zed'];
const memoryCount = ref(0); const memoryCount = ref(0);
const rebuilding = ref(false); const rebuilding = ref(false);
const version = ref('0.1.0'); const version = ref('');
onMounted(async () => { onMounted(async () => {
await loadSettings(); await loadSettings();
@@ -26,6 +26,7 @@ async function loadSettings() {
autoRefresh.value = s.autoRefresh !== false; autoRefresh.value = s.autoRefresh !== false;
editorScheme.value = s.editorScheme || 'vscode'; editorScheme.value = s.editorScheme || 'vscode';
memoryCount.value = s.memoryCount || 0; memoryCount.value = s.memoryCount || 0;
version.value = s.version || '';
} }
async function saveEditorScheme(value) { async function saveEditorScheme(value) {
@@ -187,8 +188,8 @@ function fmtRelative(iso) {
<div class="form-label">Editor URL scheme</div> <div class="form-label">Editor URL scheme</div>
<div class="form-label-hint">Clicking <code>src/app.ts:42</code> jumps to that line.</div> <div class="form-label-hint">Clicking <code>src/app.ts:42</code> jumps to that line.</div>
</div> </div>
<div class="form-control"> <div class="form-control select-control">
<select class="form-input" :value="editorScheme" @change="saveEditorScheme($event.target.value)"> <select class="path-field select-field" :value="editorScheme" @change="saveEditorScheme($event.target.value)">
<option v-for="opt in editorSchemes" :key="opt" :value="opt">{{ opt }}</option> <option v-for="opt in editorSchemes" :key="opt" :value="opt">{{ opt }}</option>
</select> </select>
</div> </div>
@@ -339,6 +340,19 @@ function fmtRelative(iso) {
.path-field:focus { outline: 0; border-color: var(--accent); background: rgba(0,0,0,0.4); box-shadow: 0 0 0 2px rgba(167,139,250,0.12); } .path-field:focus { outline: 0; border-color: var(--accent); background: rgba(0,0,0,0.4); box-shadow: 0 0 0 2px rgba(167,139,250,0.12); }
.path-field.error { border-color: rgba(248,113,113,0.4); } .path-field.error { border-color: rgba(248,113,113,0.4); }
.path-field.error:focus { border-color: #f87171; box-shadow: 0 0 0 2px rgba(248,113,113,0.12); } .path-field.error:focus { border-color: #f87171; box-shadow: 0 0 0 2px rgba(248,113,113,0.12); }
.select-control { position: relative; }
.select-control::after {
content: ''; position: absolute; right: 11px; top: 9px;
width: 6px; height: 6px; pointer-events: none;
border-right: 1px solid var(--muted); border-bottom: 1px solid var(--muted);
transform: rotate(45deg);
}
.select-field {
width: 100%; padding-right: 30px; cursor: pointer;
appearance: none; -webkit-appearance: none;
}
.select-field:hover { border-color: var(--hairline-vivid); }
.select-field option { background: var(--surface-strong); color: var(--fg); }
.tz-field { max-width: 240px; } .tz-field { max-width: 240px; }
.btn { .btn {
+32 -1
View File
@@ -109,7 +109,10 @@ function registerHandlers() {
ipcMain.handle('db:getMemories', () => []); ipcMain.handle('db:getMemories', () => []);
ipcMain.handle('db:getProjects', () => [{ project: 'quiet-zero', count: 1 }]); ipcMain.handle('db:getProjects', () => [{ project: 'quiet-zero', count: 1 }]);
ipcMain.handle('db:getStats', () => ({})); ipcMain.handle('db:getStats', () => ({}));
ipcMain.handle('settings:get', () => ({})); ipcMain.handle('settings:get', () => ({
editorScheme: 'vscode',
version: '9.8.7-test',
}));
ipcMain.handle('file-ref:open', (_event, ref) => { ipcMain.handle('file-ref:open', (_event, ref) => {
openCalls.push(ref); openCalls.push(ref);
return { opened: false }; return { opened: false };
@@ -188,6 +191,34 @@ async function run() {
assert(openCalls[0]?.sessionId === sessionId, 'click sends the session id'); assert(openCalls[0]?.sessionId === sessionId, 'click sends the session id');
assert(navigatedAway.length === 0, 'clicking a reference never navigates the window'); assert(navigatedAway.length === 0, 'clicking a reference never navigates the window');
await win.webContents.executeJavaScript(`window.location.hash = '#/settings'`, true);
await waitFor(
win.webContents,
`document.body.textContent.includes('Editor URL scheme')`,
'settings editor control',
);
const settingsState = await win.webContents.executeJavaScript(`(() => {
const select = document.querySelector('select.select-field');
const style = select ? getComputedStyle(select) : null;
return {
exists: Boolean(select),
appearance: style?.appearance || style?.webkitAppearance || null,
backgroundColor: style?.backgroundColor || null,
color: style?.color || null,
version: document.querySelector('.version-text')?.textContent?.trim() || null,
};
})()`, true);
assert(settingsState.exists, 'Settings renders the themed editor selector');
assert(settingsState.appearance === 'none', `editor selector disables native appearance (${settingsState.appearance})`);
assert(
settingsState.backgroundColor !== 'rgb(255, 255, 255)',
`editor selector keeps the dark Settings surface (${settingsState.backgroundColor})`,
);
assert(settingsState.color !== 'rgb(0, 0, 0)', `editor selector keeps themed text (${settingsState.color})`);
assert(settingsState.version === 'Obelisk 9.8.7-test', `Settings renders the IPC app version (${settingsState.version})`);
win.destroy(); win.destroy();
} }
+8 -1
View File
@@ -388,6 +388,12 @@ test('session IPC hides Codex rows by default and supports explicit source opt-i
const restore = registerMocks([ const restore = registerMocks([
[ELECTRON_URL, { [ELECTRON_URL, {
namedExports: electronNamespace({ namedExports: electronNamespace({
app: {
whenReady: () => Promise.resolve(),
on() {},
quit() {},
getVersion: () => '9.8.7-test',
},
BrowserWindow: FakeBrowserWindow, BrowserWindow: FakeBrowserWindow,
ipcMain: { ipcMain: {
handle(channel, handler) { handle(channel, handler) {
@@ -419,7 +425,8 @@ test('session IPC hides Codex rows by default and supports explicit source opt-i
assert.match(queries.at(-1).sql, /COALESCE\(source, 'claude'\) = \?/); assert.match(queries.at(-1).sql, /COALESCE\(source, 'claude'\) = \?/);
assert.ok(queries.at(-1).params.includes('codex')); assert.ok(queries.at(-1).params.includes('codex'));
await ipcHandlers.get('settings:get')(); const settings = await ipcHandlers.get('settings:get')();
assert.equal(settings.version, '9.8.7-test');
assert.ok( assert.ok(
queries.some(q => /GROUP BY COALESCE\(source, 'claude'\)/.test(q.sql)), queries.some(q => /GROUP BY COALESCE\(source, 'claude'\)/.test(q.sql)),
); );
+19
View File
@@ -0,0 +1,19 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
const source = readFileSync(
new URL('../app/src/renderer/src/views/Settings.vue', import.meta.url),
'utf8',
);
test('Settings reads the displayed version from the settings payload', () => {
assert.doesNotMatch(source, /const version = ref\(['"]0\.1\.0['"]\)/);
assert.match(source, /version\.value = s\.version/);
});
test('Editor selector uses the themed Settings control vocabulary', () => {
assert.match(source, /<select class="path-field select-field"/);
assert.match(source, /\.select-field\s*\{[^}]*appearance:\s*none/s);
assert.match(source, /\.select-control::after\s*\{/);
});