mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 07:29:42 +02:00
feat(settings): reorder App Settings, tighten the rows, add a live layout preview
The document side of the settings modal was wider than it needed to be: every row is text on the left and a switch pinned to the right, so a 960px shell plus a 62ch cap on the description left a dead gap of ~350px between the two. The shell is now 840px, the rail 196px, and descriptions run to 78ch, which closes the gap and makes the right side sit proportionally with the rail. Section order now leads with what you look at first: System (the version this install runs and whether an update is waiting, with Updates promoted above Paths/Automation/Remote access), then Terminal & Input, then Header & Panels. The modal opens scrolled to System instead of Terminal & Input. Header & Panels gains two things: - every chip carries the icon of the button it switches on, so the list reads as the header itself rather than as a column of names (File Viewer shows the folder button, Cron the clock, and so on); - a live preview above the chips: a scale model of the app with a header bar, right-docked panels, a toolbar and floating windows, rebuilt on every chip change so "what does this add" is answered in place, before saving. The preview owns no icons of its own - it CLONES `.set-chip-ico` out of the chip - so 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 by carrying `data-preview` (which slot) and `data-preview-order` (where in it); readouts that are not buttons (plan usage, CPU, font size) use `data-preview-text` instead. The frame is painted from skin tokens only, since hardcoded black alphas turned it into a grey slab on the four light skins, and it is marked `data-i18n-skip`: the mock tab names are decoration, and the labels inside are copies of chip text i18n has already translated. Cron moved into its own Scheduling group (it is a toolbar button, not a header one, and the preview places it accordingly). test/app-settings-structure.test.ts pins the new contract: the rail and the document agree on order, System leads with the version above the paths, and every previewed chip has both an icon to clone and a slot that exists.
This commit is contained in:
@@ -59,8 +59,23 @@ describe('App Settings modal structure', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('opens on Terminal & Input, so Local Echo is the first thing in reach', () => {
|
||||
expect(settingsUi).toContain("this.switchSettingsTab('settings-terminal')");
|
||||
it('opens on System, with the version and the updater above everything else', () => {
|
||||
expect(settingsUi).toContain("this.switchSettingsTab('settings-system')");
|
||||
const modal = settingsModal();
|
||||
const order = [...modal.matchAll(/<section class="set-section" id="([a-z-]+)"/g)].map((m) => m[1]);
|
||||
// Rail and document must agree, or scroll-spy paints the wrong entry.
|
||||
const rail = [...modal.matchAll(/data-section="([a-z-]+)"/g)].map((m) => m[1]);
|
||||
expect(rail.slice(0, 3)).toEqual(['settings-system', 'settings-terminal', 'settings-layout']);
|
||||
expect(order.slice(0, 3)).toEqual(['settings-system', 'settings-terminal', 'settings-layout']);
|
||||
// Updates leads the System section: version first, then the update action.
|
||||
const system = modal.match(/id="settings-system"([\s\S]*?)<\/section>/)?.[1] ?? '';
|
||||
const version = system.indexOf('id="updateCurrentVersion"');
|
||||
expect(version).toBeGreaterThan(-1);
|
||||
expect(version).toBeLessThan(system.indexOf('id="appSettingsClaudeMdPath"'));
|
||||
expect(system.indexOf('id="updateCheckBtn"')).toBeLessThan(system.indexOf('id="appSettingsClaudeMdPath"'));
|
||||
});
|
||||
|
||||
it('keeps Local Echo the first row of the second section', () => {
|
||||
const terminal = settingsModal().match(/id="settings-terminal"([\s\S]*?)<\/section>/);
|
||||
const localEcho = terminal?.[1].indexOf('appSettingsLocalEcho') ?? -1;
|
||||
const cjk = terminal?.[1].indexOf('appSettingsCjkInput') ?? -1;
|
||||
@@ -68,6 +83,33 @@ describe('App Settings modal structure', () => {
|
||||
expect(localEcho).toBeLessThan(cjk);
|
||||
});
|
||||
|
||||
it('gives every previewed chip an icon to clone, and a slot that exists', () => {
|
||||
// _syncLayoutPreview clones `.set-chip-ico` out of the chip, so a chip that
|
||||
// opts into the preview without an icon renders as an empty button, and one
|
||||
// pointing at a slot id that does not exist renders as nothing at all.
|
||||
const layout = settingsModal().match(/id="settings-layout"([\s\S]*?)<\/section>/)?.[1] ?? '';
|
||||
const chips = [...layout.matchAll(/<label class="set-chip"([^>]*)>([\s\S]*?)<\/label>/g)];
|
||||
const previewed = chips.filter(([, attrs]) => attrs.includes('data-preview='));
|
||||
expect(previewed.length).toBeGreaterThanOrEqual(15);
|
||||
for (const [, attrs, body] of previewed) {
|
||||
const kind = attrs.match(/data-preview="([a-z]+)"/)?.[1];
|
||||
expect(['header', 'panel', 'toolbar', 'float']).toContain(kind);
|
||||
expect(attrs, `chip ${body} needs a preview order`).toMatch(/data-preview-order="\d+"/);
|
||||
// A text token replaces the icon for readouts (plan usage, CPU, font size).
|
||||
const hasIcon = body.includes('class="set-chip-ico') || attrs.includes('data-preview-text=');
|
||||
expect(hasIcon, `chip ${body} has nothing to render in the preview`).toBe(true);
|
||||
}
|
||||
for (const id of [
|
||||
'appSettingsPreviewHeader',
|
||||
'appSettingsPreviewPanels',
|
||||
'appSettingsPreviewToolbar',
|
||||
'appSettingsPreviewFloats',
|
||||
]) {
|
||||
expect(layout).toContain(`id="${id}"`);
|
||||
expect(settingsUi).toContain(`'${id}'`);
|
||||
}
|
||||
});
|
||||
|
||||
it('models: keeps the 1M variants as select options behind the context switch', () => {
|
||||
const modal = settingsModal();
|
||||
const select = modal.match(/id="appSettingsClaudeModel"([\s\S]*?)<\/select>/)?.[1] ?? '';
|
||||
|
||||
Reference in New Issue
Block a user