Scale stage 2: continuous uiScale setting + the (-) 100% (+) stepper
AppSettings.textSize (compact|default|large) becomes uiScale: number, 0.5-4 in 0.1 steps, default 1. One shared clampUiScale (next to the effectiveConcurrency precedent) defines "valid" for the store, the service and the stepper: clamp to range, snap to the step grid, strip float noise, and map NaN/Infinity to 1. - SettingsStore.load migrates legacy files (compact/default/large -> 0.9/1/1.2, never over an explicit uiScale) and clamps ALWAYS, not just on migration — buildPreload writes this value into every window's root font-size, so a hand-edited settings.json (uiScale: 100) must not be able to render the UI unusable. Verified against 100, 0.001, -3, NaN, Infinity and float noise; migration verified against a real legacy file. - SettingsService: applyUiScale sets the root font-size (16 * scale, removed entirely at 1 per the delete-when-default rule). Rides the existing settingsChanged broadcast, so open editor/manager windows follow live. - buildPreload bakes the same font-size so the first paint is already scaled — without it every window opens at 100% and snaps. - Both tab-bars: the 3-button Interface row becomes the stepper. Clicking the percentage resets to 100% (tooltip says so). i18n: compact/default/large dropped, smaller/larger/reset added, all 7 locales. Verified live, win7 + modern: scale 1 is pixel-identical to the pre-work baseline (opaque interior diff exactly 0); 0.5x is a real dense mode (~2x rows); 2x scales title bar, tabs, rows, icons and controls together with text contained in its boxes — which is what stage 1c's 7.css bridge bought; 4x is enormous but coherent, and the win7 title-bar controls stay on-screen at the default window size. The known caveat stands: no minimum window size exists, so 4x in a small window is legal but cramped. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,6 @@ const SPEED_UNITS = [
|
||||
{ value: 'MBps', label: 'MB/s' },
|
||||
{ value: 'Mbps', label: 'Mbps' },
|
||||
] as const;
|
||||
const TEXT_SIZES = ['compact', 'default', 'large'] as const;
|
||||
const ICON_COLOR_MODES = ['plain', 'multi'] as const;
|
||||
const TRANSFER_MODES = ['single', 'multi'] as const;
|
||||
const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
@@ -276,20 +275,35 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interface scale: a continuous stepper, 50%..400% in 10-point
|
||||
steps. One lever moves the whole UI (root font-size; everything
|
||||
is rem), which is also why this popover itself grows with it. -->
|
||||
<div class="mt-2.5 flex items-center justify-between gap-2">
|
||||
<span class="text-xs text-muted-foreground">{{ i18n.t('options.interface.label') }}</span>
|
||||
<div class="flex rounded-md border border-border p-0.5">
|
||||
@for (size of textSizes; track size) {
|
||||
<button
|
||||
(click)="settings.setTextSize(size)"
|
||||
class="rounded px-2 py-0.5 text-[0.6875rem]"
|
||||
[class.bg-accent]="settings.textSize() === size"
|
||||
[class.text-foreground]="settings.textSize() === size"
|
||||
[class.text-muted-foreground]="settings.textSize() !== size"
|
||||
>
|
||||
{{ i18n.t('options.interface.' + size) }}
|
||||
</button>
|
||||
}
|
||||
<div class="flex items-center rounded-md border border-border p-0.5">
|
||||
<button
|
||||
(click)="settings.setUiScale(settings.uiScale() - 0.1)"
|
||||
[disabled]="settings.uiScale() <= 0.5"
|
||||
[title]="i18n.t('options.interface.smaller')"
|
||||
class="grid size-5 place-items-center rounded text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
(click)="settings.setUiScale(1)"
|
||||
[title]="i18n.t('options.interface.reset')"
|
||||
class="w-11 rounded px-1 py-0.5 text-center text-[0.6875rem] tabular-nums hover:bg-accent"
|
||||
>
|
||||
{{ scalePercent() }}
|
||||
</button>
|
||||
<button
|
||||
(click)="settings.setUiScale(settings.uiScale() + 0.1)"
|
||||
[disabled]="settings.uiScale() >= 4"
|
||||
[title]="i18n.t('options.interface.larger')"
|
||||
class="grid size-5 place-items-center rounded text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -474,7 +488,8 @@ export class TabBar {
|
||||
readonly accents = ACCENTS;
|
||||
readonly iconColorModes = ICON_COLOR_MODES;
|
||||
readonly speedUnits = SPEED_UNITS;
|
||||
readonly textSizes = TEXT_SIZES;
|
||||
/** "100%" for the stepper readout; the value itself lives in settings. */
|
||||
readonly scalePercent = computed(() => `${Math.round(this.settings.uiScale() * 100)}%`);
|
||||
readonly transferModes = TRANSFER_MODES;
|
||||
readonly concurrencyChoices = CONCURRENCY_CHOICES;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Intentionally near-empty, and that is not an oversight.
|
||||
|
||||
This skin IS the shared foundation: the OKLCH token system, `@theme inline`,
|
||||
the data-text interface sizes and the accent axis all live in ui/src/styles.css
|
||||
the panel-chrome metrics and the accent axis all live in ui/src/styles.css
|
||||
because both skins consume them, and everything else the modern look needs is
|
||||
a Tailwind utility written inline in its templates. There is no third-party
|
||||
framework to bridge and no cascade inversion to undo, so there is simply
|
||||
|
||||
@@ -30,7 +30,6 @@ const SPEED_UNITS = [
|
||||
{ value: 'MBps', label: 'MB/s' },
|
||||
{ value: 'Mbps', label: 'Mbps' },
|
||||
] as const;
|
||||
const TEXT_SIZES = ['compact', 'default', 'large'] as const;
|
||||
const ICON_COLOR_MODES = ['plain', 'multi'] as const;
|
||||
const TRANSFER_MODES = ['single', 'multi'] as const;
|
||||
const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
@@ -282,20 +281,35 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interface scale: a continuous stepper, 50%..400% in 10-point
|
||||
steps. One lever moves the whole UI (root font-size; everything
|
||||
is rem), which is also why this popover itself grows with it. -->
|
||||
<div class="mt-2.5 flex items-center justify-between gap-2">
|
||||
<span class="text-xs text-muted-foreground">{{ i18n.t('options.interface.label') }}</span>
|
||||
<div class="flex rounded-md border border-border p-0.5">
|
||||
@for (size of textSizes; track size) {
|
||||
<button
|
||||
(click)="settings.setTextSize(size)"
|
||||
class="rounded px-2 py-0.5 text-[0.6875rem]"
|
||||
[class.bg-accent]="settings.textSize() === size"
|
||||
[class.text-foreground]="settings.textSize() === size"
|
||||
[class.text-muted-foreground]="settings.textSize() !== size"
|
||||
>
|
||||
{{ i18n.t('options.interface.' + size) }}
|
||||
</button>
|
||||
}
|
||||
<div class="flex items-center rounded-md border border-border p-0.5">
|
||||
<button
|
||||
(click)="settings.setUiScale(settings.uiScale() - 0.1)"
|
||||
[disabled]="settings.uiScale() <= 0.5"
|
||||
[title]="i18n.t('options.interface.smaller')"
|
||||
class="grid size-5 place-items-center rounded text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
(click)="settings.setUiScale(1)"
|
||||
[title]="i18n.t('options.interface.reset')"
|
||||
class="w-11 rounded px-1 py-0.5 text-center text-[0.6875rem] tabular-nums hover:bg-accent"
|
||||
>
|
||||
{{ scalePercent() }}
|
||||
</button>
|
||||
<button
|
||||
(click)="settings.setUiScale(settings.uiScale() + 0.1)"
|
||||
[disabled]="settings.uiScale() >= 4"
|
||||
[title]="i18n.t('options.interface.larger')"
|
||||
class="grid size-5 place-items-center rounded text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -479,7 +493,8 @@ export class TabBar {
|
||||
readonly accents = ACCENTS;
|
||||
readonly iconColorModes = ICON_COLOR_MODES;
|
||||
readonly speedUnits = SPEED_UNITS;
|
||||
readonly textSizes = TEXT_SIZES;
|
||||
/** "100%" for the stepper readout; the value itself lives in settings. */
|
||||
readonly scalePercent = computed(() => `${Math.round(this.settings.uiScale() * 100)}%`);
|
||||
readonly transferModes = TRANSFER_MODES;
|
||||
readonly concurrencyChoices = CONCURRENCY_CHOICES;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user