Stage 1: extract the skin-agnostic UI layer into app/core/

First step of splitting the UI into two skin trees. All 12 services plus
platform.ts, format.ts, file-icons.ts, icons.ts and app.config.ts move to
ui/src/app/core/ — the layer both skins will share.

This is a pure move: git records all 17 as renames, and no file content
changed beyond import paths. The premise holds because `git diff
main...feat/7css-skin` touches none of these files — the service layer was
already skin-agnostic, so there is nothing to untangle, only to relocate.

Components now reach it through a new `@core/*` tsconfig alias rather than a
relative path. That is deliberate and load-bearing for the next stage: skin
components are about to move under app/skins/<skin>/ at varying depths, and
an alias keeps every one of these 67 imports correct wherever its file ends
up. `@shared/*` (the bun-side src/shared) already set the precedent.

Intra-core imports needed no edits at all — every ./sibling reference among
the moved files points at another moved file.

Verified: tsc clean, `bun run build:ui` clean (AOT resolves every import and
template symbol), and the running window's opaque interior is pixel-identical
to a pre-refactor capture.

Note on that comparison: the Aero frame and title bar do NOT compare equal
between launches, and that is not a regression — .window.glass::before runs
at opacity .6, so the chrome composites over whatever desktop is behind the
window and shifts by a uniform ~8% brightness. The check therefore diffs the
opaque interior (inset 30px sides, 80px top), where anything this refactor
could break would show.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-16 04:49:27 +02:00
co-authored by Claude
parent ab0bb4ef6f
commit 17bb1a14fb
33 changed files with 74 additions and 68 deletions
+11 -11
View File
@@ -1,29 +1,29 @@
import { Component, inject, signal } from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import { I18nService } from './i18n.service';
import { I18nService } from '@core/i18n.service';
import { BRANDING } from '@shared/branding';
import type { ColumnKey } from '@shared/types';
import { ConnectionsService } from './connections.service';
import { ConnectionsService } from '@core/connections.service';
import { ContextMenu } from './context-menu';
import { ChmodDialog } from './dialogs/chmod-dialog';
import { ConfirmDialog } from './dialogs/confirm-dialog';
import { OverwriteDialog } from './dialogs/overwrite-dialog';
import { PromptDialog } from './dialogs/prompt-dialog';
import { DialogsService } from './dialogs.service';
import { FileActionsService } from './file-actions.service';
import { DialogsService } from '@core/dialogs.service';
import { FileActionsService } from '@core/file-actions.service';
import { FilePanel } from './file-panel/file-panel';
import { PanelsService } from './panels.service';
import { blockNativeContextMenu } from './platform';
import { RpcService } from './rpc.service';
import { SettingsService } from './settings.service';
import { PanelsService } from '@core/panels.service';
import { blockNativeContextMenu } from '@core/platform';
import { RpcService } from '@core/rpc.service';
import { SettingsService } from '@core/settings.service';
import { StatusBar } from './status-bar/status-bar';
import { TabBar } from './tab-bar/tab-bar';
import { TitleBar } from './title-bar/title-bar';
import { ToastService } from './toast.service';
import { ToastService } from '@core/toast.service';
import { Toasts } from './toasts/toasts';
import { TransferBar } from './transfer-bar/transfer-bar';
import { TransfersService } from './transfers.service';
import { UpdaterService } from './updater.service';
import { TransfersService } from '@core/transfers.service';
import { UpdaterService } from '@core/updater.service';
@Component({
selector: 'app-root',
@@ -1,14 +1,14 @@
import { Component, computed, inject, signal } from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import { I18nService } from '../i18n.service';
import { I18nService } from '@core/i18n.service';
import type { SavedConnection, Stack } from '@shared/types';
import { ConnectionsService } from '../connections.service';
import { ConnectionsService } from '@core/connections.service';
import { ConfirmDialog } from '../dialogs/confirm-dialog';
import { PromptDialog } from '../dialogs/prompt-dialog';
import { DialogsService } from '../dialogs.service';
import { blockNativeContextMenu } from '../platform';
import { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service';
import { DialogsService } from '@core/dialogs.service';
import { blockNativeContextMenu } from '@core/platform';
import { RpcService } from '@core/rpc.service';
import { SettingsService } from '@core/settings.service';
import { TitleBar } from '../title-bar/title-bar';
import { Toasts } from '../toasts/toasts';
+2 -2
View File
@@ -1,6 +1,6 @@
import { Component, ElementRef, computed, effect, inject, signal, viewChild } from '@angular/core';
import { ContextMenuService, type MenuItem } from './context-menu.service';
import { FocusService } from './focus.service';
import { ContextMenuService, type MenuItem } from '@core/context-menu.service';
import { FocusService } from '@core/focus.service';
/** Rough per-item metrics, used only to decide whether the menu must flip. */
const ITEM_H = 24; // 7.css: 4px padding x2 + ~15px line
+3 -3
View File
@@ -1,7 +1,7 @@
import { Component, computed, inject, input, signal } from '@angular/core';
import { I18nService } from '../i18n.service';
import { DialogsService, type ChmodRequest } from '../dialogs.service';
import { FocusService } from '../focus.service';
import { I18nService } from '@core/i18n.service';
import { DialogsService, type ChmodRequest } from '@core/dialogs.service';
import { FocusService } from '@core/focus.service';
const ROLES = ['roleOwner', 'roleGroup', 'roleOthers'] as const;
const PERMS = ['r', 'w', 'x'] as const;
+3 -3
View File
@@ -1,7 +1,7 @@
import { Component, ElementRef, afterNextRender, inject, input, viewChild } from '@angular/core';
import { I18nService } from '../i18n.service';
import { DialogsService, type ConfirmRequest } from '../dialogs.service';
import { FocusService } from '../focus.service';
import { I18nService } from '@core/i18n.service';
import { DialogsService, type ConfirmRequest } from '@core/dialogs.service';
import { FocusService } from '@core/focus.service';
@Component({
selector: 'app-confirm-dialog',
+3 -3
View File
@@ -8,9 +8,9 @@ import {
viewChild,
} from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import { I18nService } from '../i18n.service';
import { DialogsService, type OverwriteChoice, type OverwriteRequest } from '../dialogs.service';
import { FocusService } from '../focus.service';
import { I18nService } from '@core/i18n.service';
import { DialogsService, type OverwriteChoice, type OverwriteRequest } from '@core/dialogs.service';
import { FocusService } from '@core/focus.service';
@Component({
selector: 'app-overwrite-dialog',
+3 -3
View File
@@ -7,9 +7,9 @@ import {
signal,
viewChild,
} from '@angular/core';
import { I18nService } from '../i18n.service';
import { DialogsService, type PromptRequest } from '../dialogs.service';
import { FocusService } from '../focus.service';
import { I18nService } from '@core/i18n.service';
import { DialogsService, type PromptRequest } from '@core/dialogs.service';
import { FocusService } from '@core/focus.service';
@Component({
selector: 'app-prompt-dialog',
+5 -5
View File
@@ -10,11 +10,11 @@ import {
viewChild,
} from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import { I18nService } from '../i18n.service';
import { ALT_LABEL, blockNativeContextMenu, EDITOR_CONTEXT, MOD_LABEL } from '../platform';
import { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service';
import { ToastService } from '../toast.service';
import { I18nService } from '@core/i18n.service';
import { ALT_LABEL, blockNativeContextMenu, EDITOR_CONTEXT, MOD_LABEL } from '@core/platform';
import { RpcService } from '@core/rpc.service';
import { SettingsService } from '@core/settings.service';
import { ToastService } from '@core/toast.service';
import { TitleBar } from '../title-bar/title-bar';
import { Toasts } from '../toasts/toasts';
+12 -12
View File
@@ -9,19 +9,19 @@ import {
viewChild,
} from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import { I18nService } from '../i18n.service';
import { I18nService } from '@core/i18n.service';
import type { ColumnKey, FileEntry, SavedConnection, SshConfigHost } from '@shared/types';
import { ConnectionsService } from '../connections.service';
import { ContextMenuService } from '../context-menu.service';
import { DialogsService } from '../dialogs.service';
import { FileActionsService } from '../file-actions.service';
import { fileGlyph, fileIconClass, fileKindKey } from '../file-icons';
import { formatMode } from '../format';
import { PanelsService, type Side, type SortKey } from '../panels.service';
import { IS_WINDOWS } from '../platform';
import { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service';
import { TransfersService } from '../transfers.service';
import { ConnectionsService } from '@core/connections.service';
import { ContextMenuService } from '@core/context-menu.service';
import { DialogsService } from '@core/dialogs.service';
import { FileActionsService } from '@core/file-actions.service';
import { fileGlyph, fileIconClass, fileKindKey } from '@core/file-icons';
import { formatMode } from '@core/format';
import { PanelsService, type Side, type SortKey } from '@core/panels.service';
import { IS_WINDOWS } from '@core/platform';
import { RpcService } from '@core/rpc.service';
import { SettingsService } from '@core/settings.service';
import { TransfersService } from '@core/transfers.service';
/** Optional columns in fixed display order, with their track widths. */
const COLUMN_DEFS: { key: ColumnKey; label: string; width: string }[] = [
+5 -5
View File
@@ -1,11 +1,11 @@
import { Component, computed, inject } from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import { I18nService } from '../i18n.service';
import { I18nService } from '@core/i18n.service';
import type { TransferSnapshot } from '@shared/types';
import { formatSpeed } from '../format';
import { PanelsService } from '../panels.service';
import { SettingsService } from '../settings.service';
import { TransfersService } from '../transfers.service';
import { formatSpeed } from '@core/format';
import { PanelsService } from '@core/panels.service';
import { SettingsService } from '@core/settings.service';
import { TransfersService } from '@core/transfers.service';
@Component({
selector: 'app-status-bar',
+6 -6
View File
@@ -3,12 +3,12 @@ import { NgIcon } from '@ng-icons/core';
import { BRANDING } from '@shared/branding';
import { LOCALE_LABELS, SUPPORTED } from '@shared/i18n';
import type { AccentTone } from '@shared/types';
import { I18nService } from '../i18n.service';
import { PanelsService } from '../panels.service';
import { MOD_LABEL } from '../platform';
import { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service';
import { UpdaterService } from '../updater.service';
import { I18nService } from '@core/i18n.service';
import { PanelsService } from '@core/panels.service';
import { MOD_LABEL } from '@core/platform';
import { RpcService } from '@core/rpc.service';
import { SettingsService } from '@core/settings.service';
import { UpdaterService } from '@core/updater.service';
const THEMES = ['system', 'light', 'dark'] as const;
const DARK_TONES = ['dark', 'darker'] as const;
+1 -1
View File
@@ -1,5 +1,5 @@
import { Component, inject, input, output, signal } from '@angular/core';
import { RpcService } from '../rpc.service';
import { RpcService } from '@core/rpc.service';
/**
* The Aero title bar. Rendered by all three window kinds. The native frame is
+1 -1
View File
@@ -1,6 +1,6 @@
import { Component, inject } from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import { ToastService } from '../toast.service';
import { ToastService } from '@core/toast.service';
/** Toast stack overlay; shared by the main window and editor windows. */
@Component({
+4 -4
View File
@@ -1,10 +1,10 @@
import { Component, inject } from '@angular/core';
import { NgIcon } from '@ng-icons/core';
import type { TransferSnapshot } from '@shared/types';
import { formatEta } from '../format';
import { I18nService } from '../i18n.service';
import { SettingsService } from '../settings.service';
import { TransfersService } from '../transfers.service';
import { formatEta } from '@core/format';
import { I18nService } from '@core/i18n.service';
import { SettingsService } from '@core/settings.service';
import { TransfersService } from '@core/transfers.service';
@Component({
selector: 'app-transfer-bar',