From 4445b715434d503cedd62b7ebd2c01fe6c700e0c Mon Sep 17 00:00:00 2001
From: Ariver <ar@MacBook-Air.local>
Date: Mon, 11 May 2026 00:31:04 +0800
Subject: [PATCH] Archive TagLauncher 4.0.7

---
 Apptag/ApptagApp.swift           |   20 ++--
 Apptag/Localization/en.json      |   10 +-
 Apptag/Localization/zh-Hant.json |   10 +-
 Apptag/Localization/ja.json      |   10 +-
 Apptag/Info.plist                |   10 +-
 Apptag/Localization/fr.json      |   10 +-
 build.sh                         |   54 ++++++++++
 Apptag/Localization/ko.json      |   10 +-
 Apptag/Localization/zh-Hans.json |   10 +-
 Apptag/ContentView.swift         |   47 +++++----
 Apptag/Localization/it.json      |   10 +-
 Apptag/Localization/es.json      |   10 +-
 Apptag/Localization/ru.json      |   10 +-
 make_dmg.sh                      |   37 +++++++
 14 files changed, 175 insertions(+), 83 deletions(-)

diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index 685804e..b572f20 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -5,7 +5,7 @@
 // MARK: - Application Entry Point
 
 @main
-struct ApptagApp: App {
+struct TagLauncherApp: App {
     @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
 
     var body: some Scene {
@@ -45,7 +45,7 @@
         setupLaunchAtLogin()
     }
 
-    /// Dock icon click → show overlay (same as menubar "Show Apptag")
+    /// Dock icon click → show overlay (same as menubar "Show TagLauncher")
     func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
         showOverlay()
         return false  // Suppress default "unhide all windows" behavior
@@ -137,9 +137,9 @@
         if let button = statusItem.button {
             button.image = NSImage(
                 systemSymbolName: "tag.fill",
-                accessibilityDescription: "Apptag"
+                accessibilityDescription: "TagLauncher"
             )
-            button.toolTip = "Apptag — Tag-based app launcher"
+            button.toolTip = "TagLauncher — Tag-based app launcher"
             button.action = #selector(toggleOverlay)
             button.target = self
         }
@@ -297,7 +297,7 @@
         hotkeyRef = ref
 
         if status != noErr {
-            print("[Apptag] Hotkey registration failed: \(status). Falling back to menu bar only.")
+            print("[TagLauncher] Hotkey registration failed: \(status). Falling back to menu bar only.")
             return
         }
 
@@ -385,7 +385,7 @@
     /// Track whether the overlay is in edit mode to suppress auto-dismiss.
     private func observeEditMode() {
         NotificationCenter.default.addObserver(
-            forName: .apptagEditModeChanged,
+            forName: .tagLauncherEditModeChanged,
             object: nil,
             queue: .main
         ) { [weak self] notification in
@@ -515,14 +515,14 @@
     private func exportTags() {
         let panel = NSSavePanel()
         panel.title = tr("settings.export")
-        panel.nameFieldStringValue = "Apptag-tags.json"
+        panel.nameFieldStringValue = "TagLauncher-tags.json"
         panel.allowedContentTypes = [.json]
         panel.begin { response in
             guard response == .OK, let url = panel.url else { return }
             do {
                 try TagDatabase.exportTo(url)
             } catch {
-                fputs("[Apptag] Export failed: \(error)\n", stderr)
+                fputs("[TagLauncher] Export failed: \(error)\n", stderr)
             }
         }
     }
@@ -538,7 +538,7 @@
                 _ = try TagDatabase.importFrom(url)
                 scanApps()
             } catch {
-                fputs("[Apptag] Import failed: \(error)\n", stderr)
+                fputs("[TagLauncher] Import failed: \(error)\n", stderr)
                 // Show alert on failure
                 let alert = NSAlert()
                 alert.messageText = tr("settings.importFailed")
@@ -694,7 +694,7 @@
                                 .frame(width: 128, height: 128)
                         }
                         VStack(alignment: .leading, spacing: 4) {
-                            Text("Apptag")
+                            Text("TagLauncher")
                                 .font(.title2)
                                 .fontWeight(.semibold)
                             Text(tr("app.description"))
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index ad7c105..8962e5a 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -4,7 +4,7 @@
 // MARK: - Notification for manual re-index
 
 extension Notification.Name {
-    static let apptagEditModeChanged = Notification.Name("ApptagEditModeChanged")
+    static let tagLauncherEditModeChanged = Notification.Name("TagLauncherEditModeChanged")
 }
 
 // MARK: - Edit Phase
@@ -267,7 +267,7 @@
         .onChange(of: editPhase) { _, newPhase in
             let active = newPhase != .none
             NotificationCenter.default.post(
-                name: .apptagEditModeChanged,
+                name: .tagLauncherEditModeChanged,
                 object: nil,
                 userInfo: ["active": active]
             )
@@ -277,7 +277,7 @@
     /// Set edit phase with synchronous notification BEFORE state change.
     func setEditPhase(_ phase: EditPhase) {
         if phase != .none {
-            NotificationCenter.default.post(name: .apptagEditModeChanged, object: nil, userInfo: ["active": true])
+            NotificationCenter.default.post(name: .tagLauncherEditModeChanged, object: nil, userInfo: ["active": true])
             NSApp.activate(ignoringOtherApps: true)
             // Always sync tag list from database when entering edit mode
             let store = TagDatabase.load()
@@ -287,7 +287,7 @@
         editPhase = phase
         if phase == .none {
             DispatchQueue.main.async {
-                NotificationCenter.default.post(name: .apptagEditModeChanged, object: nil, userInfo: ["active": false])
+                NotificationCenter.default.post(name: .tagLauncherEditModeChanged, object: nil, userInfo: ["active": false])
             }
         }
     }
@@ -482,6 +482,7 @@
         let isColorless = isColorlessContainerMode
         let isColorlessFilled = isColorless && filledColorlessContainer == group.name
         let isHovered = hoveredContainer == group.name
+        let isColorlessActive = isColorless && (isColorlessFilled || isHovered)
         let tagColor = Color(nsColor: TagColor.nsColor(for: tagColors[group.name] ?? 0))
         return VStack(alignment: .leading, spacing: 6) {
             HStack(spacing: 0) {
@@ -511,7 +512,7 @@
         .padding(16)
         .background(
             RoundedRectangle(cornerRadius: 14)
-                .fill((isColored || isColorlessFilled) ? tagColor.opacity(0.30) : Color.clear)
+                .fill((isColored || isColorlessActive) ? tagColor.opacity(0.30) : Color.clear)
                 .background(
                     RoundedRectangle(cornerRadius: 14)
                         .fill(.ultraThinMaterial)
@@ -521,22 +522,21 @@
             RoundedRectangle(cornerRadius: 14)
                 .stroke(Color.primary.opacity(0.08), lineWidth: 1)
         )
-        .shadow(color: .black.opacity(isColored && isHovered ? 0.22 : 0),
-                radius: isColored && isHovered ? 18 : 0,
-                y: isColored && isHovered ? 10 : 0)
+        .shadow(color: .black.opacity((isColored && isHovered) || isColorlessActive ? 0.22 : 0),
+                radius: (isColored && isHovered) || isColorlessActive ? 18 : 0,
+                y: (isColored && isHovered) || isColorlessActive ? 10 : 0)
         .scaleEffect(isColored && isHovered ? 1.015 : 1.0)
         .animation(.spring(response: 0.25, dampingFraction: 0.82), value: isHovered)
+        .animation(.spring(response: 0.25, dampingFraction: 0.82), value: isColorlessFilled)
         .onHover { hovering in
-            if isColored {
+            if isColored || isColorless {
                 hoveredContainer = hovering ? group.name : nil
-            } else if hovering {
-                fillColorlessContainer(group.name)
             }
         }
         .contentShape(RoundedRectangle(cornerRadius: 14))
         .onTapGesture {
-            if isColorlessFilled {
-                filledColorlessContainer = nil
+            if isColorless {
+                toggleColorlessFill(group.name)
             }
         }
     }
@@ -689,9 +689,11 @@
 
     private func gridContainerCard(_ group: TagGroup, width: CGFloat, fixedRows: Int) -> some View {
         let isColored = displayMode == "coloredGridContainer"
+        let isColorlessGrid = displayMode == "gridContainer"
         let isColorless = isColorlessContainerMode
         let isColorlessFilled = isColorless && filledColorlessContainer == group.name
         let isHovered = hoveredContainer == group.name
+        let isColorlessGridActive = isColorlessGrid && (isColorlessFilled || isHovered)
         let cols = iconColumns(width: width)
         let contentWidth = max(1, width - 32)
         let cellWidth = max(1, (contentWidth - 6 * CGFloat(cols - 1)) / CGFloat(cols))
@@ -739,7 +741,7 @@
         .padding(16)
         .background(
             RoundedRectangle(cornerRadius: 14)
-                .fill((isColored || isColorlessFilled) ? tagColor.opacity(0.30) : Color.clear)
+                .fill((isColored || isColorlessGridActive) ? tagColor.opacity(0.30) : Color.clear)
                 .background(
                     RoundedRectangle(cornerRadius: 14)
                         .fill(.ultraThinMaterial)
@@ -749,13 +751,14 @@
             RoundedRectangle(cornerRadius: 14)
                 .stroke(Color.primary.opacity(0.08), lineWidth: 1)
         )
-        .shadow(color: .black.opacity(isColored && isHovered ? 0.22 : 0),
-                radius: isColored && isHovered ? 18 : 0,
-                y: isColored && isHovered ? 10 : 0)
+        .shadow(color: .black.opacity((isColored && isHovered) || isColorlessGridActive ? 0.22 : 0),
+                radius: (isColored && isHovered) || isColorlessGridActive ? 18 : 0,
+                y: (isColored && isHovered) || isColorlessGridActive ? 10 : 0)
         .scaleEffect(isColored && isHovered ? 1.015 : 1.0)
         .animation(.spring(response: 0.25, dampingFraction: 0.82), value: isHovered)
+        .animation(.spring(response: 0.25, dampingFraction: 0.82), value: isColorlessFilled)
         .onHover { hovering in
-            if isColored {
+            if isColored || isColorlessGrid {
                 hoveredContainer = hovering ? group.name : nil
             } else if hovering {
                 fillColorlessContainer(group.name)
@@ -763,8 +766,8 @@
         }
         .contentShape(RoundedRectangle(cornerRadius: 14))
         .onTapGesture {
-            if isColorlessFilled {
-                filledColorlessContainer = nil
+            if isColorlessGrid {
+                toggleColorlessFill(group.name)
             }
         }
     }
@@ -1019,12 +1022,12 @@
     }
 
     private func fillColorlessContainer(_ id: String) {
-        guard displayMode == "container" else { return }
+        guard isColorlessContainerMode else { return }
         filledColorlessContainer = id
     }
 
     private func toggleColorlessFill(_ id: String) {
-        guard displayMode == "container" else { return }
+        guard isColorlessContainerMode else { return }
         filledColorlessContainer = (filledColorlessContainer == id) ? nil : id
     }
 
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 12d8c47..0d9c32e 100644
--- a/Apptag/Info.plist
+++ b/Apptag/Info.plist
@@ -5,9 +5,9 @@
 	<key>CFBundleDevelopmentRegion</key>
 	<string>en</string>
 	<key>CFBundleDisplayName</key>
-	<string>Apptag</string>
+	<string>TagLauncher</string>
 	<key>CFBundleExecutable</key>
-	<string>Apptag</string>
+	<string>TagLauncher</string>
 	<key>CFBundleIconFile</key>
 	<string>AppIcon</string>
 	<key>CFBundleIdentifier</key>
@@ -15,13 +15,13 @@
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>6.0</string>
 	<key>CFBundleName</key>
-	<string>Apptag</string>
+	<string>TagLauncher</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>4.0.6</string>
+	<string>4.0.7</string>
 	<key>CFBundleVersion</key>
-	<string>7</string>
+	<string>8</string>
 	<key>LSMinimumSystemVersion</key>
 	<string>26.0</string>
 	<key>NSHighResolutionCapable</key>
diff --git a/Apptag/Localization/en.json b/Apptag/Localization/en.json
index 3755216..aab8b35 100644
--- a/Apptag/Localization/en.json
+++ b/Apptag/Localization/en.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "Show Apptag",
+  "menu.show": "Show TagLauncher",
   "menu.preferences": "Preferences…",
   "menu.quit": "Quit",
   "menu.language": "Language",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "App list style:",
   "settings.flat": "Flat",
   "settings.container": "Colorless Container",
-  "settings.flatDesc": "\"Flat\" shows apps directly. \"Colorless Container\" and \"Colored Container\" use masonry containers. \"Colorless Grid Container\" and \"Colored Grid Container\" align app icons in equal-height grid rows.",
+  "settings.flatDesc": "\"Flat\" shows apps directly. \"Colorless Container\" and \"Colored Container\" use masonry containers. \"Colorless Grid\" and \"Colored Grid\" align app icons in equal-height grid rows.",
   "settings.hideAppNames": "Hide app names",
   "settings.tagPosition": "Tag position:",
   "settings.left": "Left",
@@ -43,7 +43,7 @@
   "tag.cancel": "Cancel",
   "tag.add": "Add",
   "tag.newName": "New tag name",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "Tag-based app launcher",
   "app.version": "Version",
   "app.build": "Build",
@@ -57,6 +57,6 @@
   "tag.system": "System",
   "tag.productivity": "Productivity",
   "settings.coloredContainer": "Colored Container",
-  "settings.gridContainer": "Colorless Grid Container",
-  "settings.coloredGridContainer": "Colored Grid Container"
+  "settings.gridContainer": "Colorless Grid",
+  "settings.coloredGridContainer": "Colored Grid"
 }
diff --git a/Apptag/Localization/es.json b/Apptag/Localization/es.json
index 0de0c8a..7a27672 100644
--- a/Apptag/Localization/es.json
+++ b/Apptag/Localization/es.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "Mostrar Apptag",
+  "menu.show": "Mostrar TagLauncher",
   "menu.preferences": "Preferencias…",
   "menu.quit": "Salir",
   "menu.language": "Idioma",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "Estilo de lista de apps:",
   "settings.flat": "Plano",
   "settings.container": "Contenedor sin color",
-  "settings.flatDesc": "\"Plano\" muestra las apps directamente. \"Contenedor sin color\" y \"Contenedor coloreado\" usan contenedores en cascada. \"Contenedor de cuadrícula sin color\" y \"Contenedor de cuadrícula coloreado\" alinean los iconos en filas de cuadrícula de igual altura.",
+  "settings.flatDesc": "\"Plano\" muestra las apps directamente. \"Contenedor sin color\" y \"Contenedor coloreado\" usan contenedores en cascada. \"Cuadrícula sin color\" y \"Cuadrícula coloreada\" alinean los iconos en filas de cuadrícula de igual altura.",
   "settings.hideAppNames": "Ocultar nombres de apps",
   "settings.tagPosition": "Posición de etiquetas:",
   "settings.left": "Izquierda",
@@ -43,7 +43,7 @@
   "tag.cancel": "Cancelar",
   "tag.add": "Añadir",
   "tag.newName": "Nuevo nombre de etiqueta",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "Lanzador de apps basado en etiquetas",
   "app.version": "Versión",
   "app.build": "Build",
@@ -57,6 +57,6 @@
   "tag.system": "Sistema",
   "tag.productivity": "Oficina",
   "settings.coloredContainer": "Contenedor coloreado",
-  "settings.gridContainer": "Contenedor de cuadrícula sin color",
-  "settings.coloredGridContainer": "Contenedor de cuadrícula coloreado"
+  "settings.gridContainer": "Cuadrícula sin color",
+  "settings.coloredGridContainer": "Cuadrícula coloreada"
 }
diff --git a/Apptag/Localization/fr.json b/Apptag/Localization/fr.json
index 2b60867..e5664be 100644
--- a/Apptag/Localization/fr.json
+++ b/Apptag/Localization/fr.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "Afficher Apptag",
+  "menu.show": "Afficher TagLauncher",
   "menu.preferences": "Préférences…",
   "menu.quit": "Quitter",
   "menu.language": "Langue",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "Style de liste d'applications :",
   "settings.flat": "Liste",
   "settings.container": "Conteneur sans couleur",
-  "settings.flatDesc": "\"Liste\" affiche les apps directement. \"Conteneur sans couleur\" et \"Conteneur coloré\" utilisent des conteneurs en cascade. \"Conteneur grille sans couleur\" et \"Conteneur grille coloré\" alignent les icônes dans des lignes de grille de hauteur égale.",
+  "settings.flatDesc": "\"Liste\" affiche les apps directement. \"Conteneur sans couleur\" et \"Conteneur coloré\" utilisent des conteneurs en cascade. \"Grille sans couleur\" et \"Grille colorée\" alignent les icônes dans des lignes de grille de hauteur égale.",
   "settings.hideAppNames": "Masquer les noms d'apps",
   "settings.tagPosition": "Position des étiquettes :",
   "settings.left": "Gauche",
@@ -43,7 +43,7 @@
   "tag.cancel": "Annuler",
   "tag.add": "Ajouter",
   "tag.newName": "Nouveau nom d'étiquette",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "Lanceur d'applications par étiquettes",
   "app.version": "Version",
   "app.build": "Build",
@@ -57,6 +57,6 @@
   "tag.system": "Système",
   "tag.productivity": "Bureau",
   "settings.coloredContainer": "Conteneur coloré",
-  "settings.gridContainer": "Conteneur grille sans couleur",
-  "settings.coloredGridContainer": "Conteneur grille coloré"
+  "settings.gridContainer": "Grille sans couleur",
+  "settings.coloredGridContainer": "Grille colorée"
 }
diff --git a/Apptag/Localization/it.json b/Apptag/Localization/it.json
index 6aa41b6..b4ca461 100644
--- a/Apptag/Localization/it.json
+++ b/Apptag/Localization/it.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "Mostra Apptag",
+  "menu.show": "Mostra TagLauncher",
   "menu.preferences": "Preferenze…",
   "menu.quit": "Esci",
   "menu.language": "Lingua",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "Stile elenco app:",
   "settings.flat": "Elenco",
   "settings.container": "Contenitore senza colore",
-  "settings.flatDesc": "\"Elenco\" mostra le app direttamente. \"Contenitore senza colore\" e \"Contenitore colorato\" usano contenitori a cascata. \"Contenitore griglia senza colore\" e \"Contenitore griglia colorato\" allineano le icone in righe griglia di uguale altezza.",
+  "settings.flatDesc": "\"Elenco\" mostra le app direttamente. \"Contenitore senza colore\" e \"Contenitore colorato\" usano contenitori a cascata. \"Griglia senza colore\" e \"Griglia colorata\" allineano le icone in righe griglia di uguale altezza.",
   "settings.hideAppNames": "Nascondi nomi app",
   "settings.tagPosition": "Posizione tag:",
   "settings.left": "Sinistra",
@@ -43,7 +43,7 @@
   "tag.cancel": "Annulla",
   "tag.add": "Aggiungi",
   "tag.newName": "Nuovo nome tag",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "Launcher di app basato su tag",
   "app.version": "Versione",
   "app.build": "Build",
@@ -57,6 +57,6 @@
   "tag.system": "Sistema",
   "tag.productivity": "Ufficio",
   "settings.coloredContainer": "Contenitore colorato",
-  "settings.gridContainer": "Contenitore griglia senza colore",
-  "settings.coloredGridContainer": "Contenitore griglia colorato"
+  "settings.gridContainer": "Griglia senza colore",
+  "settings.coloredGridContainer": "Griglia colorata"
 }
diff --git a/Apptag/Localization/ja.json b/Apptag/Localization/ja.json
index 1dde2f2..21ec766 100644
--- a/Apptag/Localization/ja.json
+++ b/Apptag/Localization/ja.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "Apptagを表示",
+  "menu.show": "TagLauncherを表示",
   "menu.preferences": "設定…",
   "menu.quit": "終了",
   "menu.language": "言語",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "アプリ一覧スタイル:",
   "settings.flat": "フラット",
   "settings.container": "無色コンテナ",
-  "settings.flatDesc": "\"フラット\"はアプリを直接表示します。\"無色コンテナ\"と\"カラーコンテナ\"はコンテナを滝状に配置します。\"無色グリッドコンテナ\"と\"カラーグリッドコンテナ\"は等高のグリッド行でアプリアイコンを揃えます。",
+  "settings.flatDesc": "\"フラット\"はアプリを直接表示します。\"無色コンテナ\"と\"カラーコンテナ\"はコンテナを滝状に配置します。\"無色グリッド\"と\"カラーグリッド\"は等高のグリッド行でアプリアイコンを揃えます。",
   "settings.hideAppNames": "アプリ名を非表示",
   "settings.tagPosition": "タグ位置:",
   "settings.left": "左",
@@ -43,7 +43,7 @@
   "tag.cancel": "キャンセル",
   "tag.add": "追加",
   "tag.newName": "新しいタグ名",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "タグベースのアプリランチャー",
   "app.version": "バージョン",
   "app.build": "ビルド",
@@ -57,6 +57,6 @@
   "tag.system": "システム",
   "tag.productivity": "オフィス",
   "settings.coloredContainer": "カラーコンテナ",
-  "settings.gridContainer": "無色グリッドコンテナ",
-  "settings.coloredGridContainer": "カラーグリッドコンテナ"
+  "settings.gridContainer": "無色グリッド",
+  "settings.coloredGridContainer": "カラーグリッド"
 }
diff --git a/Apptag/Localization/ko.json b/Apptag/Localization/ko.json
index 46419f9..6d144c7 100644
--- a/Apptag/Localization/ko.json
+++ b/Apptag/Localization/ko.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "Apptag 보기",
+  "menu.show": "TagLauncher 보기",
   "menu.preferences": "환경설정…",
   "menu.quit": "종료",
   "menu.language": "언어",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "앱 목록 스타일:",
   "settings.flat": "플랫",
   "settings.container": "무색 컨테이너",
-  "settings.flatDesc": "\"플랫\"은 앱을 직접 표시합니다. \"무색 컨테이너\"와 \"색상 컨테이너\"는 컨테이너를 폭포식으로 배치합니다. \"무색 그리드 컨테이너\"와 \"색상 그리드 컨테이너\"는 같은 높이의 그리드 행으로 앱 아이콘을 정렬합니다.",
+  "settings.flatDesc": "\"플랫\"은 앱을 직접 표시합니다. \"무색 컨테이너\"와 \"색상 컨테이너\"는 컨테이너를 폭포식으로 배치합니다. \"무색 그리드\"와 \"색상 그리드\"는 같은 높이의 그리드 행으로 앱 아이콘을 정렬합니다.",
   "settings.hideAppNames": "앱 이름 숨기기",
   "settings.tagPosition": "태그 위치:",
   "settings.left": "왼쪽",
@@ -43,7 +43,7 @@
   "tag.cancel": "취소",
   "tag.add": "추가",
   "tag.newName": "새 태그 이름",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "태그 기반 앱 런처",
   "app.version": "버전",
   "app.build": "빌드",
@@ -57,6 +57,6 @@
   "tag.system": "시스템",
   "tag.productivity": "오피스",
   "settings.coloredContainer": "색상 컨테이너",
-  "settings.gridContainer": "무색 그리드 컨테이너",
-  "settings.coloredGridContainer": "색상 그리드 컨테이너"
+  "settings.gridContainer": "무색 그리드",
+  "settings.coloredGridContainer": "색상 그리드"
 }
diff --git a/Apptag/Localization/ru.json b/Apptag/Localization/ru.json
index 5e9b2a1..a6922f9 100644
--- a/Apptag/Localization/ru.json
+++ b/Apptag/Localization/ru.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "Показать Apptag",
+  "menu.show": "Показать TagLauncher",
   "menu.preferences": "Настройки…",
   "menu.quit": "Выйти",
   "menu.language": "Язык",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "Стиль списка приложений:",
   "settings.flat": "Плоский",
   "settings.container": "Бесцветный контейнер",
-  "settings.flatDesc": "\"Плоский\" показывает приложения напрямую. \"Бесцветный контейнер\" и \"Цветной контейнер\" используют каскадные контейнеры. \"Бесцветный сеточный контейнер\" и \"Цветной сеточный контейнер\" выравнивают значки приложений в строках сетки одинаковой высоты.",
+  "settings.flatDesc": "\"Плоский\" показывает приложения напрямую. \"Бесцветный контейнер\" и \"Цветной контейнер\" используют каскадные контейнеры. \"Бесцветная сетка\" и \"Цветная сетка\" выравнивают значки приложений в строках сетки одинаковой высоты.",
   "settings.hideAppNames": "Скрыть названия приложений",
   "settings.tagPosition": "Положение тегов:",
   "settings.left": "Слева",
@@ -43,7 +43,7 @@
   "tag.cancel": "Отмена",
   "tag.add": "Добавить",
   "tag.newName": "Новое название тега",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "Лаунчер приложений на основе тегов",
   "app.version": "Версия",
   "app.build": "Сборка",
@@ -57,6 +57,6 @@
   "tag.system": "Система",
   "tag.productivity": "Офис",
   "settings.coloredContainer": "Цветной контейнер",
-  "settings.gridContainer": "Бесцветный сеточный контейнер",
-  "settings.coloredGridContainer": "Цветной сеточный контейнер"
+  "settings.gridContainer": "Бесцветная сетка",
+  "settings.coloredGridContainer": "Цветная сетка"
 }
diff --git a/Apptag/Localization/zh-Hans.json b/Apptag/Localization/zh-Hans.json
index 6a4462d..49091fe 100644
--- a/Apptag/Localization/zh-Hans.json
+++ b/Apptag/Localization/zh-Hans.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "显示 Apptag",
+  "menu.show": "显示 TagLauncher",
   "menu.preferences": "偏好设置…",
   "menu.quit": "退出",
   "menu.language": "语言",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "应用列表样式:",
   "settings.flat": "平铺",
   "settings.container": "无色容器",
-  "settings.flatDesc": "\"平铺\"直接显示应用。\"无色容器\"和\"彩色容器\"使用瀑布流容器。\"无色网格容器\"和\"彩色网格容器\"会按等高网格行对齐应用图标。",
+  "settings.flatDesc": "\"平铺\"直接显示应用。\"无色容器\"和\"彩色容器\"使用瀑布流容器。\"无色网格\"和\"彩色网格\"会按等高网格行对齐应用图标。",
   "settings.hideAppNames": "隐藏应用名称",
   "settings.tagPosition": "标签位置:",
   "settings.left": "左侧",
@@ -43,7 +43,7 @@
   "tag.cancel": "取消",
   "tag.add": "添加",
   "tag.newName": "新标签名称",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "基于标签的应用启动器",
   "app.version": "版本",
   "app.build": "构建",
@@ -57,6 +57,6 @@
   "tag.system": "系统优化",
   "tag.productivity": "办公",
   "settings.coloredContainer": "彩色容器",
-  "settings.gridContainer": "无色网格容器",
-  "settings.coloredGridContainer": "彩色网格容器"
+  "settings.gridContainer": "无色网格",
+  "settings.coloredGridContainer": "彩色网格"
 }
diff --git a/Apptag/Localization/zh-Hant.json b/Apptag/Localization/zh-Hant.json
index 5c76051..d284885 100644
--- a/Apptag/Localization/zh-Hant.json
+++ b/Apptag/Localization/zh-Hant.json
@@ -1,5 +1,5 @@
 {
-  "menu.show": "顯示 Apptag",
+  "menu.show": "顯示 TagLauncher",
   "menu.preferences": "偏好設定…",
   "menu.quit": "退出",
   "menu.language": "語言",
@@ -13,7 +13,7 @@
   "settings.appListStyle": "應用程式列表樣式:",
   "settings.flat": "平鋪",
   "settings.container": "無色容器",
-  "settings.flatDesc": "\"平鋪\"直接顯示應用程式。\"無色容器\"和\"彩色容器\"使用瀑布流容器。\"無色網格容器\"和\"彩色網格容器\"會按等高網格列對齊應用程式圖示。",
+  "settings.flatDesc": "\"平鋪\"直接顯示應用程式。\"無色容器\"和\"彩色容器\"使用瀑布流容器。\"無色網格\"和\"彩色網格\"會按等高網格列對齊應用程式圖示。",
   "settings.hideAppNames": "隱藏應用程式名稱",
   "settings.tagPosition": "標籤位置:",
   "settings.left": "左側",
@@ -43,7 +43,7 @@
   "tag.cancel": "取消",
   "tag.add": "新增",
   "tag.newName": "新標籤名稱",
-  "app.name": "Apptag",
+  "app.name": "TagLauncher",
   "app.description": "基於標籤的應用程式啟動器",
   "app.version": "版本",
   "app.build": "建置",
@@ -57,6 +57,6 @@
   "tag.system": "系統優化",
   "tag.productivity": "辦公",
   "settings.coloredContainer": "彩色容器",
-  "settings.gridContainer": "無色網格容器",
-  "settings.coloredGridContainer": "彩色網格容器"
+  "settings.gridContainer": "無色網格",
+  "settings.coloredGridContainer": "彩色網格"
 }
diff --git a/build.sh b/build.sh
index 970a371..6f525da 100644
--- a/build.sh
+++ b/build.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 set -euo pipefail
 
-APP_NAME="Apptag"
+APP_NAME="TagLauncher"
 PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
 BUILD_DIR="$PROJECT_DIR/build"
 APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
@@ -11,6 +11,20 @@
 
 SDK_PATH=$(xcrun --sdk macosx --show-sdk-path)
 TARGET="arm64-apple-macosx26.0"
+
+# --- Optional: App Store / Sandbox signing ---
+# Set CODESIGN_IDENTITY to your "Apple Distribution" or "Mac Developer" cert name.
+# Example: CODESIGN_IDENTITY="Apple Distribution: Your Name (TEAMID)"
+CODESIGN_IDENTITY="${CODESIGN_IDENTITY:-}"
+ENTITLEMENTS="$SWIFT_DIR/TagLauncher.entitlements"
+APP_STORE_MODE=false
+
+if [ -n "${APP_STORE:-}" ] && [ "$APP_STORE" = "1" ]; then
+    APP_STORE_MODE=true
+    if [ -z "$CODESIGN_IDENTITY" ]; then
+        echo "⚠️  APP_STORE=1 but CODESIGN_IDENTITY not set. Will ad-hoc sign with entitlements."
+    fi
+fi
 
 echo "==> Cleaning..."
 rm -rf "$BUILD_DIR"
@@ -27,6 +41,7 @@
     -framework Carbon \
     -sdk "$SDK_PATH" \
     -target "$TARGET" \
+    -Osize \
     "$SWIFT_DIR"/*.swift
 
 echo "==> Copying Info.plist..."
@@ -42,9 +57,46 @@
 echo "==> Copying Localization files..."
 cp -r "$SWIFT_DIR/Localization" "$RESOURCES_DIR/Localization"
 
+echo "==> Embedding PkgInfo..."
+echo -n "APPL????" > "$APP_BUNDLE/Contents/PkgInfo"
+
 echo "==> Setting permissions..."
 chmod +x "$MACOS_DIR/$APP_NAME"
+
+echo "==> Stripping debug symbols..."
+strip -x "$MACOS_DIR/$APP_NAME"
+
+# --- Signing ---
+if [ "$APP_STORE_MODE" = true ] || [ -n "$CODESIGN_IDENTITY" ]; then
+    echo "==> Signing with entitlements..."
+    if [ -n "$CODESIGN_IDENTITY" ]; then
+        CODESIGN_ARGS="--sign \"$CODESIGN_IDENTITY\""
+    else
+        CODESIGN_ARGS="--sign -"
+    fi
+
+    if [ -f "$ENTITLEMENTS" ]; then
+        eval codesign --force --deep $CODESIGN_ARGS --entitlements "$ENTITLEMENTS" "$APP_BUNDLE"
+        echo "   Signed with entitlements: $ENTITLEMENTS"
+    else
+        eval codesign --force --deep $CODESIGN_ARGS "$APP_BUNDLE"
+        echo "   Signed (no entitlements file found)"
+    fi
+else
+    # Default: ad-hoc sign without entitlements (local dev)
+    echo "==> Ad-hoc signing..."
+    codesign --force --deep --sign - "$APP_BUNDLE"
+fi
 
 echo ""
 echo "✅ App built at: $APP_BUNDLE"
 echo "   Run: open $APP_BUNDLE"
+
+# --- App Store mode: remind about next steps ---
+if [ "$APP_STORE_MODE" = true ]; then
+    echo ""
+    echo "📦 App Store build ready. Next steps:"
+    echo "   1. Verify sandbox: spctl -a -v \"$APP_BUNDLE\""
+    echo "   2. Test in sandbox: sandbox-exec -f <(echo '(version 1)(allow default)') open \"$APP_BUNDLE\""
+    echo "   3. Upload via Transporter or 'xcrun altool --upload-app'"
+fi
diff --git a/make_dmg.sh b/make_dmg.sh
new file mode 100755
index 0000000..afdb242
--- /dev/null
+++ b/make_dmg.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -euo pipefail
+
+PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
+APP_NAME="TagLauncher"
+BUILD_DIR="$PROJECT_DIR/build"
+APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
+DMG_NAME="$APP_NAME.dmg"
+DMG_PATH="$BUILD_DIR/$DMG_NAME"
+STAGING="$BUILD_DIR/dmg_staging"
+
+echo "==> Preparing DMG staging..."
+rm -rf "$STAGING"
+mkdir -p "$STAGING"
+
+echo "==> Copying $APP_NAME.app..."
+cp -R "$APP_BUNDLE" "$STAGING/"
+
+echo "==> Creating Applications symlink..."
+ln -s /Applications "$STAGING/Applications"
+
+echo "==> Creating DMG..."
+hdiutil create \
+    -volname "$APP_NAME" \
+    -srcfolder "$STAGING" \
+    -ov \
+    -format UDZO \
+    -imagekey zlib-level=9 \
+    "$DMG_PATH"
+
+echo "==> Cleaning up staging..."
+rm -rf "$STAGING"
+
+SIZE=$(du -sh "$DMG_PATH" | awk '{print $1}')
+echo ""
+echo "✅ DMG created: $DMG_PATH ($SIZE)"
+echo "   Open: open $DMG_PATH"

--
Gitblit v1.9.3