Rename the darker tone to AMOLED; file icons are always colored

- Tone: the "darker" option is labelled "AMOLED" in all 7 locales — it is
  the true-black AMOLED palette. The persisted value stays "darker", so no
  settings migration.
- File icons: the Plain/Multi option is gone and the per-category glyphs +
  colors are always on. Removed the iconColors setting (type, default,
  signal, setter, options row, strings) and the plain branch in
  file-icons.ts. A stale "iconColors" key in settings.json is carried
  harmlessly and dropped on the next save.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-23 16:30:55 +02:00
co-authored by Claude
parent e3a5750b01
commit a83264c77d
15 changed files with 18 additions and 59 deletions
+2 -2
View File
@@ -234,8 +234,8 @@ component; tokens → `ui/src/styles.css`.
(dotted lookup, `{param}` interpolation, `{one,other}` plural maps via (dotted lookup, `{param}` interpolation, `{one,other}` plural maps via
`Intl.PluralRules`); missing keys fall back to English then the raw key. `Intl.PluralRules`); missing keys fall back to English then the raw key.
**Reactive by design**: `t()` reads the `locale` signal, so every template **Reactive by design**: `t()` reads the `locale` signal, so every template
binding that calls it re-renders live on a language switch (same call-stack binding that calls it re-renders live on a language switch (call-stack
tracking as `iconClass`) — no pipe. Also exposes locale-aware `date()`/`size()` tracking, see gotchas.md) — no pipe. Also exposes locale-aware `date()`/`size()`
/`speed()` so the file list reformats on switch. `SettingsService.applyLocale` /`speed()` so the file list reformats on switch. `SettingsService.applyLocale`
resolves the `locale` setting (`system` → `navigator.language`) into it, on resolves the `locale` setting (`system` → `navigator.language`) into it, on
boot and on the `settingsChanged` broadcast (so editor/manager windows follow). boot and on the `settingsChanged` broadcast (so editor/manager windows follow).
+3 -2
View File
@@ -20,8 +20,9 @@ touching the adjacent code.
`[class.shadow-[...]]` (brackets) does NOT parse — use `[style.box-shadow]`. `[class.shadow-[...]]` (brackets) does NOT parse — use `[style.box-shadow]`.
Static class attributes may use arbitrary values freely. Static class attributes may use arbitrary values freely.
- Functions called from templates ARE reactive: signals read inside them - Functions called from templates ARE reactive: signals read inside them
register on the view's consumer (call-stack-based tracking). That's why register on the view's consumer (call-stack-based tracking). That's why a
`iconClass(entry)` reacting to `settings.iconColors()` works. template calling `i18n.t()` re-renders on a language switch — `t()` reads
the `locale` signal.
- Dedicated tools > shell: IDE diagnostics between paired template/class - Dedicated tools > shell: IDE diagnostics between paired template/class
edits are often stale mid-sequence — trust `bun run build:ui` (full AOT edits are often stale mid-sequence — trust `bun run build:ui` (full AOT
type-check), not the first red squiggle. type-check), not the first red squiggle.
+1 -2
View File
@@ -34,7 +34,7 @@ export const de = {
language: "Sprache", language: "Sprache",
system: "System", system: "System",
theme: { label: "Design", system: "System", light: "Hell", dark: "Dunkel" }, theme: { label: "Design", system: "System", light: "Hell", dark: "Dunkel" },
tone: { label: "Ton", dark: "Dunkel", darker: "Dunkler" }, tone: { label: "Ton", dark: "Dunkel", darker: "AMOLED" },
accent: { label: "Akzent" }, accent: { label: "Akzent" },
accentNames: { accentNames: {
graphite: "Graphit", graphite: "Graphit",
@@ -46,7 +46,6 @@ export const de = {
green: "Grün", green: "Grün",
teal: "Petrol", teal: "Petrol",
}, },
fileIcons: { label: "Datei-Symbole", plain: "Einfach", multi: "Farbig" },
interface: { label: "Oberfläche", smaller: "Kleiner", larger: "Größer", reset: "Auf 100% zurücksetzen" }, interface: { label: "Oberfläche", smaller: "Kleiner", larger: "Größer", reset: "Auf 100% zurücksetzen" },
speedUnit: { label: "Geschwindigkeitseinheit" }, speedUnit: { label: "Geschwindigkeitseinheit" },
transfers: { label: "Übertragungen", single: "Einzeln", multi: "Mehrere" }, transfers: { label: "Übertragungen", single: "Einzeln", multi: "Mehrere" },
+1 -2
View File
@@ -38,7 +38,7 @@ export const en = {
language: "Language", language: "Language",
system: "System", system: "System",
theme: { label: "Theme", system: "System", light: "Light", dark: "Dark" }, theme: { label: "Theme", system: "System", light: "Light", dark: "Dark" },
tone: { label: "Tone", dark: "Dark", darker: "Darker" }, tone: { label: "Tone", dark: "Dark", darker: "AMOLED" },
accent: { label: "Accent" }, accent: { label: "Accent" },
accentNames: { accentNames: {
graphite: "Graphite", graphite: "Graphite",
@@ -50,7 +50,6 @@ export const en = {
green: "Green", green: "Green",
teal: "Teal", teal: "Teal",
}, },
fileIcons: { label: "File icons", plain: "Plain", multi: "Multi" },
interface: { label: "Interface", smaller: "Smaller", larger: "Larger", reset: "Reset to 100%" }, interface: { label: "Interface", smaller: "Smaller", larger: "Larger", reset: "Reset to 100%" },
speedUnit: { label: "Speed unit" }, speedUnit: { label: "Speed unit" },
transfers: { label: "Transfers", single: "Single", multi: "Multi" }, transfers: { label: "Transfers", single: "Single", multi: "Multi" },
+1 -2
View File
@@ -34,7 +34,7 @@ export const es = {
language: "Idioma", language: "Idioma",
system: "Sistema", system: "Sistema",
theme: { label: "Tema", system: "Sistema", light: "Claro", dark: "Oscuro" }, theme: { label: "Tema", system: "Sistema", light: "Claro", dark: "Oscuro" },
tone: { label: "Tono", dark: "Oscuro", darker: "Más oscuro" }, tone: { label: "Tono", dark: "Oscuro", darker: "AMOLED" },
accent: { label: "Acento" }, accent: { label: "Acento" },
accentNames: { accentNames: {
graphite: "Grafito", graphite: "Grafito",
@@ -46,7 +46,6 @@ export const es = {
green: "Verde", green: "Verde",
teal: "Turquesa", teal: "Turquesa",
}, },
fileIcons: { label: "Iconos", plain: "Simple", multi: "Color" },
interface: { label: "Interfaz", smaller: "Más pequeña", larger: "Más grande", reset: "Restablecer al 100%" }, interface: { label: "Interfaz", smaller: "Más pequeña", larger: "Más grande", reset: "Restablecer al 100%" },
speedUnit: { label: "Unidad de velocidad" }, speedUnit: { label: "Unidad de velocidad" },
transfers: { label: "Transferencias", single: "Una", multi: "Varias" }, transfers: { label: "Transferencias", single: "Una", multi: "Varias" },
+1 -2
View File
@@ -36,7 +36,7 @@ export const eu = {
language: "Hizkuntza", language: "Hizkuntza",
system: "Sistema", system: "Sistema",
theme: { label: "Gaia", system: "Sistema", light: "Argia", dark: "Iluna" }, theme: { label: "Gaia", system: "Sistema", light: "Argia", dark: "Iluna" },
tone: { label: "Tonua", dark: "Iluna", darker: "Ilunagoa" }, tone: { label: "Tonua", dark: "Iluna", darker: "AMOLED" },
accent: { label: "Azentua" }, accent: { label: "Azentua" },
accentNames: { accentNames: {
graphite: "Grafitoa", graphite: "Grafitoa",
@@ -48,7 +48,6 @@ export const eu = {
green: "Berdea", green: "Berdea",
teal: "Turkesa", teal: "Turkesa",
}, },
fileIcons: { label: "Fitxategi-ikonoak", plain: "Soila", multi: "Koloretsua" },
interface: { label: "Interfazea", smaller: "Txikiagoa", larger: "Handiagoa", reset: "Berrezarri %100era" }, interface: { label: "Interfazea", smaller: "Txikiagoa", larger: "Handiagoa", reset: "Berrezarri %100era" },
speedUnit: { label: "Abiadura-unitatea" }, speedUnit: { label: "Abiadura-unitatea" },
transfers: { label: "Transferentziak", single: "Banaka", multi: "Anitz" }, transfers: { label: "Transferentziak", single: "Banaka", multi: "Anitz" },
+1 -2
View File
@@ -34,7 +34,7 @@ export const fr = {
language: "Langue", language: "Langue",
system: "Système", system: "Système",
theme: { label: "Thème", system: "Système", light: "Clair", dark: "Sombre" }, theme: { label: "Thème", system: "Système", light: "Clair", dark: "Sombre" },
tone: { label: "Ton", dark: "Sombre", darker: "Plus sombre" }, tone: { label: "Ton", dark: "Sombre", darker: "AMOLED" },
accent: { label: "Accent" }, accent: { label: "Accent" },
accentNames: { accentNames: {
graphite: "Graphite", graphite: "Graphite",
@@ -46,7 +46,6 @@ export const fr = {
green: "Vert", green: "Vert",
teal: "Sarcelle", teal: "Sarcelle",
}, },
fileIcons: { label: "Icônes", plain: "Simple", multi: "Couleur" },
interface: { label: "Interface", smaller: "Plus petite", larger: "Plus grande", reset: "Réinitialiser à 100%" }, interface: { label: "Interface", smaller: "Plus petite", larger: "Plus grande", reset: "Réinitialiser à 100%" },
speedUnit: { label: "Unité de vitesse" }, speedUnit: { label: "Unité de vitesse" },
transfers: { label: "Transferts", single: "Un", multi: "Plusieurs" }, transfers: { label: "Transferts", single: "Un", multi: "Plusieurs" },
+1 -2
View File
@@ -35,7 +35,7 @@ export const ja = {
language: "言語", language: "言語",
system: "システム", system: "システム",
theme: { label: "テーマ", system: "システム", light: "ライト", dark: "ダーク" }, theme: { label: "テーマ", system: "システム", light: "ライト", dark: "ダーク" },
tone: { label: "トーン", dark: "ダーク", darker: "より暗く" }, tone: { label: "トーン", dark: "ダーク", darker: "AMOLED" },
accent: { label: "アクセント" }, accent: { label: "アクセント" },
accentNames: { accentNames: {
graphite: "グラファイト", graphite: "グラファイト",
@@ -47,7 +47,6 @@ export const ja = {
green: "グリーン", green: "グリーン",
teal: "ティール", teal: "ティール",
}, },
fileIcons: { label: "ファイルアイコン", plain: "シンプル", multi: "カラー" },
interface: { label: "インターフェース", smaller: "縮小", larger: "拡大", reset: "100%に戻す" }, interface: { label: "インターフェース", smaller: "縮小", larger: "拡大", reset: "100%に戻す" },
speedUnit: { label: "速度の単位" }, speedUnit: { label: "速度の単位" },
transfers: { label: "転送", single: "1件ずつ", multi: "複数" }, transfers: { label: "転送", single: "1件ずつ", multi: "複数" },
+1 -2
View File
@@ -35,7 +35,7 @@ export const zh = {
language: "语言", language: "语言",
system: "系统", system: "系统",
theme: { label: "主题", system: "系统", light: "浅色", dark: "深色" }, theme: { label: "主题", system: "系统", light: "浅色", dark: "深色" },
tone: { label: "色调", dark: "深色", darker: "更深" }, tone: { label: "色调", dark: "深色", darker: "AMOLED" },
accent: { label: "强调色" }, accent: { label: "强调色" },
accentNames: { accentNames: {
graphite: "石墨", graphite: "石墨",
@@ -47,7 +47,6 @@ export const zh = {
green: "绿色", green: "绿色",
teal: "青色", teal: "青色",
}, },
fileIcons: { label: "文件图标", plain: "简约", multi: "彩色" },
interface: { label: "界面", smaller: "缩小", larger: "放大", reset: "重置为100%" }, interface: { label: "界面", smaller: "缩小", larger: "放大", reset: "重置为100%" },
speedUnit: { label: "速度单位" }, speedUnit: { label: "速度单位" },
transfers: { label: "传输", single: "单个", multi: "多个" }, transfers: { label: "传输", single: "单个", multi: "多个" },
-3
View File
@@ -83,8 +83,6 @@ export interface AppSettings {
darkTone: "dark" | "darker"; darkTone: "dark" | "darker";
/** Accent tone for chrome and selection tint; graphite = neutral zinc. */ /** Accent tone for chrome and selection tint; graphite = neutral zinc. */
accent: AccentTone; accent: AccentTone;
/** File-list icons: plain neutral glyphs, or per-category glyphs and colors. */
iconColors: "plain" | "multi";
/** Visible optional file-list columns (both panels; Name always shows). */ /** Visible optional file-list columns (both panels; Name always shows). */
columns: ColumnKey[]; columns: ColumnKey[];
speedUnit: "MBps" | "Mbps"; speedUnit: "MBps" | "Mbps";
@@ -116,7 +114,6 @@ export const DEFAULT_SETTINGS: AppSettings = {
theme: "system", theme: "system",
darkTone: "dark", darkTone: "dark",
accent: "blue", accent: "blue",
iconColors: "multi",
columns: ["size", "mtime"], columns: ["size", "mtime"],
speedUnit: "MBps", speedUnit: "MBps",
uiScale: 1, uiScale: 1,
+3 -7
View File
@@ -1,4 +1,4 @@
// File-list glyphs + per-category colors ("Multi" icon mode). Category colors // File-list glyphs + per-category colors. Category colors
// are fixed tokens (never follow the accent) so a folder is always folder-blue. // are fixed tokens (never follow the accent) so a folder is always folder-blue.
// Only the icon glyph is colored; row text stays neutral. Docs and unknown // Only the icon glyph is colored; row text stays neutral. Docs and unknown
// files stay muted on purpose — sparing color is what keeps it scannable. // files stay muted on purpose — sparing color is what keeps it scannable.
@@ -8,7 +8,6 @@ type Category = 'image' | 'audio' | 'video' | 'archive' | 'code' | 'doc';
// Class strings are literals so the Tailwind scanner picks them up. // Class strings are literals so the Tailwind scanner picks them up.
const MUTED = 'shrink-0 text-muted-foreground'; const MUTED = 'shrink-0 text-muted-foreground';
const PRIMARY = 'shrink-0 text-primary';
const FOLDER = 'shrink-0 text-icon-folder'; const FOLDER = 'shrink-0 text-icon-folder';
const LINK = 'shrink-0 text-icon-link'; const LINK = 'shrink-0 text-icon-link';
@@ -85,18 +84,15 @@ export function fileKindKey(entry: FileEntry): string {
return category ? KIND_KEYS[category] : 'file'; return category ? KIND_KEYS[category] : 'file';
} }
export function fileGlyph(entry: FileEntry, multi: boolean): string { export function fileGlyph(entry: FileEntry): string {
if (entry.name === '..') return 'mynaArrowUp'; if (entry.name === '..') return 'mynaArrowUp';
if (entry.type === 'dir') return 'mynaFolder'; if (entry.type === 'dir') return 'mynaFolder';
if (!multi) return 'mynaFile';
if (entry.type === 'symlink') return 'mynaLink'; if (entry.type === 'symlink') return 'mynaLink';
const category = categoryOf(entry.name); const category = categoryOf(entry.name);
return category ? CATEGORIES[category].glyph : 'mynaFile'; return category ? CATEGORIES[category].glyph : 'mynaFile';
} }
export function fileIconClass(entry: FileEntry, multi: boolean): string { export function fileIconClass(entry: FileEntry): string {
// plain mode: exactly the pre-color look ('..' has type 'dir' -> primary)
if (!multi) return entry.type === 'dir' ? PRIMARY : MUTED;
if (entry.name === '..') return MUTED; if (entry.name === '..') return MUTED;
if (entry.type === 'dir') return FOLDER; if (entry.type === 'dir') return FOLDER;
if (entry.type === 'symlink') return LINK; if (entry.type === 'symlink') return LINK;
-9
View File
@@ -10,7 +10,6 @@ export class SettingsService {
readonly theme = signal<AppSettings['theme']>(DEFAULT_SETTINGS.theme); readonly theme = signal<AppSettings['theme']>(DEFAULT_SETTINGS.theme);
readonly darkTone = signal<AppSettings['darkTone']>(DEFAULT_SETTINGS.darkTone); readonly darkTone = signal<AppSettings['darkTone']>(DEFAULT_SETTINGS.darkTone);
readonly accent = signal<AppSettings['accent']>(DEFAULT_SETTINGS.accent); readonly accent = signal<AppSettings['accent']>(DEFAULT_SETTINGS.accent);
readonly iconColors = signal<AppSettings['iconColors']>(DEFAULT_SETTINGS.iconColors);
readonly columns = signal<ColumnKey[]>(DEFAULT_SETTINGS.columns); readonly columns = signal<ColumnKey[]>(DEFAULT_SETTINGS.columns);
readonly speedUnit = signal<AppSettings['speedUnit']>(DEFAULT_SETTINGS.speedUnit); readonly speedUnit = signal<AppSettings['speedUnit']>(DEFAULT_SETTINGS.speedUnit);
readonly uiScale = signal<number>(DEFAULT_SETTINGS.uiScale); readonly uiScale = signal<number>(DEFAULT_SETTINGS.uiScale);
@@ -53,7 +52,6 @@ export class SettingsService {
this.theme.set(settings.theme); this.theme.set(settings.theme);
this.darkTone.set(settings.darkTone); this.darkTone.set(settings.darkTone);
this.accent.set(settings.accent); this.accent.set(settings.accent);
this.iconColors.set(settings.iconColors);
this.columns.set(settings.columns); this.columns.set(settings.columns);
this.speedUnit.set(settings.speedUnit); this.speedUnit.set(settings.speedUnit);
this.uiScale.set(settings.uiScale); this.uiScale.set(settings.uiScale);
@@ -93,12 +91,6 @@ export class SettingsService {
void this.persist(); void this.persist();
} }
setIconColors(mode: AppSettings['iconColors']): void {
// no apply step — the file panels read the signal reactively
this.iconColors.set(mode);
void this.persist();
}
toggleColumn(key: ColumnKey): void { toggleColumn(key: ColumnKey): void {
this.columns.update((list) => this.columns.update((list) =>
list.includes(key) ? list.filter((c) => c !== key) : [...list, key], list.includes(key) ? list.filter((c) => c !== key) : [...list, key],
@@ -193,7 +185,6 @@ export class SettingsService {
theme: this.theme(), theme: this.theme(),
darkTone: this.darkTone(), darkTone: this.darkTone(),
accent: this.accent(), accent: this.accent(),
iconColors: this.iconColors(),
columns: this.columns(), columns: this.columns(),
speedUnit: this.speedUnit(), speedUnit: this.speedUnit(),
uiScale: this.uiScale(), uiScale: this.uiScale(),
@@ -177,11 +177,11 @@ export class FilePanel {
} }
iconFor(entry: FileEntry): string { iconFor(entry: FileEntry): string {
return fileGlyph(entry, this.settings.iconColors() === 'multi'); return fileGlyph(entry);
} }
iconClass(entry: FileEntry): string { iconClass(entry: FileEntry): string {
return fileIconClass(entry, this.settings.iconColors() === 'multi'); return fileIconClass(entry);
} }
isSelected(entry: FileEntry): boolean { isSelected(entry: FileEntry): boolean {
@@ -30,7 +30,6 @@ const SPEED_UNITS = [
{ value: 'MBps', label: 'MB/s' }, { value: 'MBps', label: 'MB/s' },
{ value: 'Mbps', label: 'Mbps' }, { value: 'Mbps', label: 'Mbps' },
] as const; ] as const;
const ICON_COLOR_MODES = ['plain', 'multi'] as const;
const TRANSFER_MODES = ['single', 'multi'] as const; const TRANSFER_MODES = ['single', 'multi'] as const;
const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const; const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
@@ -242,23 +241,6 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
</div> </div>
</div> </div>
<div class="mt-2.5 flex items-center justify-between gap-2">
<span class="text-xs text-muted-foreground">{{ i18n.t('options.fileIcons.label') }}</span>
<div class="flex rounded-md border border-border p-0.5">
@for (mode of iconColorModes; track mode) {
<button
(click)="settings.setIconColors(mode)"
class="rounded px-2 py-0.5 text-[0.6875rem]"
[class.bg-accent]="settings.iconColors() === mode"
[class.text-foreground]="settings.iconColors() === mode"
[class.text-muted-foreground]="settings.iconColors() !== mode"
>
{{ i18n.t('options.fileIcons.' + mode) }}
</button>
}
</div>
</div>
<!-- Interface scale: a continuous stepper, 50%..400% in 10-point <!-- Interface scale: a continuous stepper, 50%..400% in 10-point
steps. One lever moves the whole UI (root font-size; everything steps. One lever moves the whole UI (root font-size; everything
is rem), which is also why this popover itself grows with it. --> is rem), which is also why this popover itself grows with it. -->
@@ -463,7 +445,6 @@ export class TabBar {
readonly themes = THEMES; readonly themes = THEMES;
readonly darkTones = DARK_TONES; readonly darkTones = DARK_TONES;
readonly accents = ACCENTS; readonly accents = ACCENTS;
readonly iconColorModes = ICON_COLOR_MODES;
readonly speedUnits = SPEED_UNITS; readonly speedUnits = SPEED_UNITS;
/** "100%" for the stepper readout; the value itself lives in settings. */ /** "100%" for the stepper readout; the value itself lives in settings. */
readonly scalePercent = computed(() => `${Math.round(this.settings.uiScale() * 100)}%`); readonly scalePercent = computed(() => `${Math.round(this.settings.uiScale() * 100)}%`);
+1 -1
View File
@@ -30,7 +30,7 @@
--input: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067); --ring: oklch(0.705 0.015 286.067);
--radius: 0.5rem; --radius: 0.5rem;
/* file-type icon colors ("Multi" mode) — fixed category hues, muted chroma */ /* file-type icon colors — fixed category hues, muted chroma */
--icon-folder: oklch(0.58 0.13 250); --icon-folder: oklch(0.58 0.13 250);
--icon-link: oklch(0.56 0.1 190); --icon-link: oklch(0.56 0.1 190);
--icon-image: oklch(0.58 0.13 295); --icon-image: oklch(0.58 0.13 295);