From e1226e6ae5a75388fe5d1a0e1ed008f80fee25ed Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 17 May 2026 14:21:25 +0800
Subject: [PATCH] Release Apptag 6.0.5 build 610

---
 Apptag/ApptagApp.swift            |   15 
 Apptag/Localization/cs.json       |   22 
 Apptag/Localization/no.json       |   22 
 Apptag/AppGridItem.swift          |  103 +++
 Apptag/Localization/zh-Hant.json  |   22 
 Apptag/Localization/ja.json       |   22 
 Apptag/Info.plist                 |    4 
 Apptag/Localization/th.json       |   22 
 Apptag/Localization/nl.json       |   22 
 Apptag/Localization/sr-Cyrl.json  |   22 
 Apptag/Localization/zh-Hans.json  |   22 
 Apptag/Localization/de.json       |   22 
 Apptag/Localization/ms.json       |   22 
 Apptag/Localization/nn.json       |   22 
 Apptag/Localization/pt-BR.json    |   22 
 Apptag/Localization/es.json       |   22 
 Apptag/Localization/tr.json       |   22 
 AppStore_Submission.md            |   51 -
 Apptag/Localization/en.json       |   22 
 Apptag/Localization/nb.json       |   22 
 Apptag/Localization/ar.json       |   22 
 Apptag/Localization/vi.json       |   22 
 Apptag/Localization/fr.json       |   22 
 Apptag/Localization/id.json       |   22 
 Apptag/Localization/da.json       |   22 
 CHANGELOG.md                      |   31 +
 Apptag/Localization/uk.json       |   22 
 Apptag/Localization/ar-Najdi.json |   22 
 Apptag/TagGroupView.swift         |    4 
 Apptag/Localization/ko.json       |   22 
 Apptag/ContentView.swift          |  593 ++++++++++++++++++-
 Apptag/DataLayer.swift            |  273 ++++++++
 Apptag/Localization/it.json       |   22 
 Apptag/Localization/sv.json       |   22 
 Apptag/Localization/ro.json       |   22 
 Apptag/Localization/pl.json       |   22 
 Apptag/Localization/ru.json       |   22 
 37 files changed, 1,578 insertions(+), 134 deletions(-)

diff --git a/AppStore_Submission.md b/AppStore_Submission.md
index 8681c7b..5d9a815 100644
--- a/AppStore_Submission.md
+++ b/AppStore_Submission.md
@@ -1,6 +1,6 @@
 # Apptag — Mac App Store 提审资料
 
-> 版本: 5.7.8 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-15
+> 版本: 6.0.5 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-17
 
 ---
 
@@ -11,8 +11,8 @@
 | **App 名称** | Apptag |
 | **副标题** | Tag-Based App Launcher |
 | **Bundle ID** | com.apptag.launcher |
-| **版本号** | 5.7.8 |
-| **Build 号** | 583 |
+| **版本号** | 6.0.5 |
+| **Build 号** | 610 |
 | **SKU** | apptag-mac-001 |
 | **主要类别** | Utilities (工具) |
 | **次要类别** | Productivity (效率) |
@@ -187,36 +187,23 @@
 ## 8. What's New (本次版本更新说明)
 
 ```
-Apptag 5.7.8
+Apptag 6.0.5
 
-- Fixed duplicate overlay keyboard event monitors that could accumulate after repeatedly opening TagLauncher
-- Removed obsolete tag reorder fallback handlers that could interfere with click handling after drag sorting
-- Reduced unnecessary Dock refreshes when unrelated preferences change
-- Made the menu bar icon mandatory and removed the Show Menu Bar Icon preference across all localizations
-- Removed unused macOS app-menu items and added a localized Show App List command with symbol-only shortcut glyphs
-- Replaced the default macOS Services/Hide menu groups and made the menu bar status item use a fixed visible icon slot
-- Replaced the menu bar SF Symbol with a code-drawn template icon so the status item remains visible even if symbol rendering fails
-- Improved the General preferences layout for long localized text by using two-column style choices and aligned setting rows
-- Refined the app-list style chooser so Flat spans the first row and the remaining four styles form a balanced 2x2 grid
-- Reduced the app-list style option height and added compact visual glyphs for flat, masonry, and grid layouts
-- Added a symbol-only shortcut hint on the Language, Data, and About preference tabs for Option + Shift + Space
-- Fixed the menu bar icon visibility issue
-- Fixed Preferences placement when opened above TagLauncher from a fullscreen app Space
-- Fixed tag navigation reorder mode so clicking elsewhere exits the shaking state
-- Expanded localization support to 29 language variants
-- Updated the Language preferences tab to a two-column layout for better readability
-- Added long-press drag sorting for tag navigation in normal app-list views
-- Tag sorting now works from top, left, or right tag navigation positions across all 5 app-list styles
-- Tag reorder animations now update both the tag navigation and matching app containers
-- Added automatic migration for legacy non-sandbox tag databases into the App Store sandbox container
-- Added a dedicated Language tab in Preferences with live language switching
-- Preferences now opens centered above the active TagLauncher overlay, including multi-display setups
-- Improved tag reordering in edit mode with live visual feedback and synchronized group layout updates
-- Renamed backup actions to Export/Import Categories & Layout across all supported languages
-- Added Mac App Store sandbox entitlements and App Store build validation
-- Hid Launch at Login controls in sandboxed App Store builds
-- Removed deprecated app launch API usage
-- Version kept at 5.7.8, Build updated to 583
+- Added an Uncommon app marker for apps you are still learning, with optional hover notes
+- New apps can be marked as Uncommon automatically and are cleared after enough launches from TagLauncher
+- Added local note editing for Uncommon apps; notes stay in the exported JSON even if the marker is removed later
+- Added an Uncommon bubble toggle in the Data preferences tab, enabled by default
+- Reworked batch category editing into clear Add Tags and Remove Tags modes
+- Fixed batch editing so selected apps no longer inherit unrelated tags from each other
+- Added clear confirmation feedback showing the real number of tag links added or removed
+- Improved Remove Tags mode with red delete indicators and safer label truncation
+- Fixed edit-sidebar layout so long localized hints no longer stretch the page
+- Removed the edit-page center title and gave more room to the action hint and confirmation button
+- Improved shortcut hints in Language, Data, and About settings pages using symbol-only key graphics
+- Fixed a crash caused by toolbar hint layout constraints in the edit screen
+- Fixed Safari and other modern macOS system apps being missed when they live behind Cryptex system paths
+- Kept duplicate app icons out by resolving symlinks for scanning deduplication
+- Version updated to 6.0.5, Build updated to 610
 ```
 
 ---
diff --git a/Apptag/AppGridItem.swift b/Apptag/AppGridItem.swift
index 3b4cc5a..9fa97e2 100644
--- a/Apptag/AppGridItem.swift
+++ b/Apptag/AppGridItem.swift
@@ -10,10 +10,14 @@
     var sourceTag: String? = nil
     var dragModeActive: Bool = false
     var onDragModeChange: ((Bool) -> Void)? = nil
+    var onBubbleHover: ((AppInfo, CGRect, Bool) -> Void)? = nil
+    var onEditNote: ((AppInfo, CGRect) -> Void)? = nil
     let onSelect: () -> Void
 
     @State private var isHovered = false
     @State private var wiggle = false
+    @State private var globalFrame: CGRect = .zero
+    @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = true
 
     static let hoverScale: CGFloat = 1.22
     static let labelHeight: CGFloat = 14
@@ -62,9 +66,28 @@
         .padding(.vertical, 8)
         .padding(.horizontal, 4)
         .frame(width: Self.stableWidth(iconSize: iconSize), height: Self.stableHeight(iconSize: iconSize))
+        .background(
+            GeometryReader { proxy in
+                Color.clear
+                    .onAppear { globalFrame = proxy.frame(in: .global) }
+                    .onChange(of: proxy.frame(in: .global)) { _, newFrame in
+                        globalFrame = newFrame
+                    }
+            }
+        )
         .contentShape(Rectangle())
         .onHover { hovering in
             isHovered = hovering
+            if showUncommonAppBubbles && app.isUncommon {
+                onBubbleHover?(app, globalFrame, hovering)
+            }
+        }
+        .contextMenu {
+            if app.isUncommon {
+                Button(tr("appNote.edit")) {
+                    onEditNote?(app, globalFrame)
+                }
+            }
         }
         .rotationEffect(.degrees(dragModeActive ? (wiggle ? 2.0 : -2.0) : 0))
         .animation(
@@ -79,6 +102,86 @@
     }
 }
 
+enum BubblePlacement {
+    case above
+    case below
+}
+
+struct AppNameBubble: View {
+    let appName: String
+    let note: String?
+    let isEditing: Bool
+    let placement: BubblePlacement
+    @Binding var draftNote: String
+    var noteFocused: FocusState<Bool>.Binding
+    let onCommit: () -> Void
+    let onCancel: () -> Void
+
+    private var displayNote: String {
+        note?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+    }
+
+    var body: some View {
+        content
+        .onExitCommand(perform: onCancel)
+    }
+
+    private var content: some View {
+        VStack(spacing: isEditing || !displayNote.isEmpty ? 8 : 0) {
+            Text(appName)
+                .font(.system(size: isEditing ? 22 : 24, weight: .bold))
+                .foregroundStyle(.white)
+                .lineLimit(1)
+                .truncationMode(.middle)
+                .frame(maxWidth: .infinity)
+
+            if isEditing {
+                VStack(alignment: .trailing, spacing: 4) {
+                    TextField(tr("appNote.placeholder"), text: limitedDraft)
+                        .textFieldStyle(.plain)
+                        .font(.system(size: 15, weight: .medium))
+                        .foregroundStyle(.white)
+                        .padding(.horizontal, 12)
+                        .frame(height: 36)
+                        .background(
+                            RoundedRectangle(cornerRadius: 9)
+                                .fill(Color.white.opacity(0.12))
+                        )
+                        .focused(noteFocused)
+                        .onSubmit(onCommit)
+                    Text("\(draftNote.count) / \(TagDatabase.maxAppNoteLength)")
+                        .font(.system(size: 10, weight: .medium))
+                        .foregroundStyle(.white.opacity(0.35))
+                }
+            } else if !displayNote.isEmpty {
+                Text(displayNote)
+                    .font(.system(size: 14, weight: .medium))
+                    .lineSpacing(2)
+                    .foregroundStyle(.white.opacity(0.78))
+                    .lineLimit(4)
+                    .multilineTextAlignment(.center)
+                    .fixedSize(horizontal: false, vertical: true)
+                    .frame(maxWidth: .infinity)
+            }
+        }
+        .padding(.horizontal, isEditing ? 22 : 24)
+        .padding(.vertical, isEditing ? 18 : 16)
+        .background(
+            RoundedRectangle(cornerRadius: 18, style: .continuous)
+                .fill(Color.black.opacity(0.92))
+                .shadow(color: .black.opacity(0.34), radius: 24, y: 16)
+                .shadow(color: .black.opacity(0.18), radius: 8, y: 3)
+        )
+    }
+
+    private var limitedDraft: Binding<String> {
+        Binding(
+            get: { draftNote },
+            set: { draftNote = String($0.prefix(TagDatabase.maxAppNoteLength)) }
+        )
+    }
+}
+
 private struct DraggableAppIconView: NSViewRepresentable {
     let icon: NSImage
     let iconSize: CGFloat
diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index a04f8b8..e40b773 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -796,6 +796,7 @@
     @AppStorage("hideAppNames") private var hideAppNames = false
     @AppStorage("showDockIcon") private var showDockIcon = false
     @AppStorage("launchAtLogin") private var launchAtLogin = true
+    @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = true
     @State private var selectedLanguage = L10n.currentCode
     @State private var isRefreshingLanguage = false
     @State private var allApps: [AppInfo] = []
@@ -1064,7 +1065,19 @@
 
             // Tab 4: Data
             VStack(spacing: 0) {
-                Spacer(minLength: 96)
+                Spacer(minLength: 54)
+
+                VStack(alignment: .leading, spacing: 5) {
+                    Toggle(tr("settings.uncommonBubble"), isOn: $showUncommonAppBubbles)
+                        .font(.system(size: 13, weight: .medium))
+                    Text(tr("settings.uncommonBubbleDesc"))
+                        .font(.caption)
+                        .foregroundStyle(.secondary)
+                        .fixedSize(horizontal: false, vertical: true)
+                }
+                .frame(width: 420, alignment: .leading)
+
+                Spacer(minLength: 28)
 
                 VStack(alignment: .leading, spacing: 8) {
                     Text(tr("settings.backup"))
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index cc4bdea..7b6037e 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -16,6 +16,11 @@
     case editingApps
 }
 
+enum EditTagOperation {
+    case add
+    case remove
+}
+
 // MARK: - Native NSTextField (avoids SwiftUI TextField event issues)
 
 /// Custom container that wraps NSTextField so that hitTest returns the container
@@ -220,6 +225,64 @@
 
 // MARK: - Full-Screen Overlay
 
+private struct AppBubbleContext {
+    let app: AppInfo
+    let frame: CGRect
+}
+
+private struct EditActionFeedback: Identifiable {
+    let id = UUID()
+    let title: String
+    let message: String
+}
+
+private struct EditActionFeedbackBubble: View {
+    let title: String
+    let message: String
+    let onClose: () -> Void
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 14) {
+            HStack(alignment: .top, spacing: 12) {
+                Text(title)
+                    .font(.system(size: 24, weight: .bold))
+                    .foregroundStyle(.white)
+                    .frame(maxWidth: .infinity, alignment: .leading)
+
+                Button(action: onClose) {
+                    Image(systemName: "xmark")
+                        .font(.system(size: 12, weight: .bold))
+                        .foregroundStyle(.white.opacity(0.82))
+                        .frame(width: 26, height: 26)
+                        .background(
+                            Circle()
+                                .fill(Color.white.opacity(0.12))
+                        )
+                }
+                .buttonStyle(.plain)
+            }
+
+            ScrollView(.vertical, showsIndicators: true) {
+                Text(message)
+                    .font(.system(size: 15, weight: .medium))
+                    .lineSpacing(3)
+                    .foregroundStyle(.white.opacity(0.84))
+                    .multilineTextAlignment(.leading)
+                    .frame(maxWidth: .infinity, alignment: .leading)
+            }
+            .frame(maxHeight: 220)
+        }
+        .padding(.horizontal, 24)
+        .padding(.vertical, 20)
+        .background(
+            RoundedRectangle(cornerRadius: 18, style: .continuous)
+                .fill(Color.black.opacity(0.92))
+                .shadow(color: .black.opacity(0.34), radius: 24, y: 16)
+                .shadow(color: .black.opacity(0.18), radius: 8, y: 3)
+        )
+    }
+}
+
 struct ContentView: View {
     let hideOverlay: () -> Void
 
@@ -231,7 +294,9 @@
     @State private var editPhase: EditPhase = .none
     @State private var selectedAppPaths: Set<URL> = []
     @State private var selectedTagNames: Set<String> = []
-    @State private var successToast: String? = nil
+    @State private var removableTagNames: Set<String> = []
+    @State private var editTagOperation: EditTagOperation = .add
+    @State private var editActionFeedback: EditActionFeedback? = nil
     @State private var draggedTagNames: [String] = []  // live drag order
     @State private var dragItem: String? = nil          // currently dragged tag
     @State private var tagReorderFrames: [String: CGRect] = [:]
@@ -246,6 +311,10 @@
     @State private var dropRefreshVisible = false
     @State private var dropRefreshStartedAt: Date? = nil
     @State private var layoutRefreshID = UUID()
+    @State private var hoveredBubble: AppBubbleContext? = nil
+    @State private var editingBubble: AppBubbleContext? = nil
+    @State private var bubbleDraftNote = ""
+    @FocusState private var bubbleNoteFocused: Bool
 
     // Configurable defaults
     @AppStorage("defaultGroupName") private var defaultGroupName = "Other"
@@ -255,6 +324,9 @@
     @State private var notchHeight: CGFloat = 0
     @AppStorage("displayMode") private var displayMode = "flat"
     @AppStorage("hideAppNames") private var hideAppNames = false
+
+    private let editSidebarWidth: CGFloat = 188
+    private let editSidebarHorizontalInset: CGFloat = 12
 
     private var isSideLayout: Bool {
         tagPosition == "left" || tagPosition == "right"
@@ -292,6 +364,9 @@
             case .editingApps:
                 editAppsView
             }
+
+            uncommonAppBubbleOverlay
+            editActionFeedbackOverlay
 
             if let message = dropWarningToast {
                 Text(message)
@@ -341,11 +416,17 @@
         }
         .onChange(of: editPhase) { _, newPhase in
             let active = newPhase != .none
+            dismissAppBubble()
             NotificationCenter.default.post(
                 name: .tagLauncherEditModeChanged,
                 object: nil,
                 userInfo: ["active": active]
             )
+        }
+        .onChange(of: bubbleNoteFocused) { _, focused in
+            if editingBubble != nil && !focused {
+                commitBubbleNote()
+            }
         }
     }
 
@@ -502,6 +583,43 @@
         .id(layoutRefreshID)
     }
 
+    private var uncommonAppBubbleOverlay: some View {
+        GeometryReader { proxy in
+            if let context = editingBubble ?? hoveredBubble {
+                let rootFrame = proxy.frame(in: .global)
+                let editing = editingBubble != nil
+                let width = min(editing ? 440 : 520, max(260, proxy.size.width - 48))
+                let placement = bubblePlacement(for: context.frame, rootFrame: rootFrame)
+                let metrics = bubbleMetrics(
+                    for: context,
+                    rootFrame: rootFrame,
+                    rootSize: proxy.size,
+                    width: width,
+                    placement: placement,
+                    isEditing: editing
+                )
+
+                AppNameBubble(
+                    appName: context.app.name,
+                    note: currentNote(for: context.app),
+                    isEditing: editing,
+                    placement: placement,
+                    draftNote: $bubbleDraftNote,
+                    noteFocused: $bubbleNoteFocused,
+                    onCommit: commitBubbleNote,
+                    onCancel: dismissAppBubble
+                )
+                .frame(width: width)
+                .fixedSize(horizontal: false, vertical: true)
+                .position(x: metrics.centerX, y: metrics.centerY)
+                .transition(.scale(scale: 0.94).combined(with: .opacity))
+                .zIndex(500)
+            }
+        }
+        .ignoresSafeArea()
+        .allowsHitTesting(editingBubble != nil)
+    }
+
     private var flatGrid: some View {
         ScrollViewReader { proxy in
             ScrollView {
@@ -515,6 +633,8 @@
                             showNames: !hideAppNames,
                             dragModeActive: appDragModeActive,
                             onDragModeChange: { setAppDragMode($0) },
+                            onBubbleHover: handleBubbleHover,
+                            onEditNote: beginEditingBubbleNote,
                             onDropApp: { path, source, copy in
                                 dropApp(path: path, sourceTag: source, targetTag: group.name, copy: copy)
                             }
@@ -615,6 +735,8 @@
                         sourceTag: group.name,
                         dragModeActive: appDragModeActive,
                         onDragModeChange: { setAppDragMode($0) },
+                        onBubbleHover: handleBubbleHover,
+                        onEditNote: beginEditingBubbleNote,
                         onSelect: { openApp(app) }
                     )
                 }
@@ -643,6 +765,7 @@
         .shadow(color: .black.opacity((isColored && isHovered) || isColorlessActive ? 0.22 : 0),
                 radius: (isColored && isHovered) || isColorlessActive ? 18 : 0,
                 y: (isColored && isHovered) || isColorlessActive ? 10 : 0)
+        .zIndex(isHovered ? 50 : 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)
@@ -849,6 +972,8 @@
                                 sourceTag: group.name,
                                 dragModeActive: appDragModeActive,
                                 onDragModeChange: { setAppDragMode($0) },
+                                onBubbleHover: handleBubbleHover,
+                                onEditNote: beginEditingBubbleNote,
                                 onSelect: { openApp(app) }
                             )
                                 .frame(width: cellWidth)
@@ -889,6 +1014,7 @@
         .shadow(color: .black.opacity((isColored && isHovered) || isColorlessGridActive ? 0.22 : 0),
                 radius: (isColored && isHovered) || isColorlessGridActive ? 18 : 0,
                 y: (isColored && isHovered) || isColorlessGridActive ? 10 : 0)
+        .zIndex(isHovered ? 50 : 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)
@@ -959,18 +1085,26 @@
 
     private var editAppsView: some View {
         VStack(spacing: 0) {
-            HStack {
-                Button { cancelEditApps(); setEditPhase(.none) } label: {
-                    Label(tr("edit.exit"), systemImage: "rectangle.portrait.and.arrow.right")
-                        .font(.system(size: 12))
+            HStack(alignment: .center, spacing: 14) {
+                HStack(alignment: .center, spacing: 12) {
+                    Button { cancelEditApps(); setEditPhase(.none) } label: {
+                        Label(tr("edit.exit"), systemImage: "rectangle.portrait.and.arrow.right")
+                            .font(.system(size: 12))
+                    }
+                    .buttonStyle(.bordered)
+
+                    operationHeaderPicker
                 }
-                .buttonStyle(.bordered)
-                Spacer()
-                Text(tr("edit.title")).font(.headline)
-                Spacer()
-                Button(tr("edit.confirm")) { confirmAssign() }
+
+                Spacer(minLength: 16)
+
+                headerModeHintView
+
+                Button(editConfirmTitle) { confirmAssign() }
                     .buttonStyle(.borderedProminent)
-                    .disabled(selectedAppPaths.isEmpty)
+                    .disabled(selectedAppPaths.isEmpty || pendingOperationTagNames.isEmpty)
+                    .fixedSize(horizontal: true, vertical: false)
+                    .layoutPriority(2)
             }
             .padding(.horizontal, 24)
             .padding(.top, notchHeight > 0 ? notchHeight + 10 : 20)
@@ -980,8 +1114,28 @@
 
             HStack(spacing: 0) {
                 VStack(alignment: .leading, spacing: 4) {
-                    Text(tr("edit.selectTags")).font(.caption).foregroundStyle(.secondary).padding(.bottom, 4)
-                    Text(tr("edit.dragHint")).font(.caption2).foregroundStyle(.tertiary).padding(.bottom, 2)
+                    VStack(alignment: .leading, spacing: 6) {
+                        Text(tr("edit.selectTags"))
+                            .font(.caption)
+                            .foregroundStyle(.secondary)
+                            .lineLimit(2)
+                            .fixedSize(horizontal: false, vertical: true)
+                            .multilineTextAlignment(.leading)
+                            .frame(maxWidth: .infinity, alignment: .leading)
+
+                        Text(tr("edit.dragHint"))
+                            .font(.caption2)
+                            .foregroundStyle(.tertiary)
+                            .lineLimit(nil)
+                            .fixedSize(horizontal: false, vertical: true)
+                            .multilineTextAlignment(.leading)
+                            .frame(maxWidth: .infinity, alignment: .leading)
+                    }
+                    .padding(.horizontal, editSidebarHorizontalInset)
+                    .padding(.top, 4)
+                    .padding(.bottom, 4)
+                    .frame(width: editSidebarWidth, alignment: .leading)
+
                     ScrollView(.vertical, showsIndicators: false) {
                         VStack(spacing: 4) {
                             ForEach(sortedTagNames, id: \.self) { tagName in
@@ -1006,8 +1160,9 @@
                             tagReorderFrames = frames
                         }
                     }
-                    .frame(width: 155)
+                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                 }
+                .frame(width: editSidebarWidth, alignment: .topLeading)
                 Rectangle().fill(.secondary.opacity(0.12)).frame(width: 1)
 
                 if allApps.isEmpty {
@@ -1017,29 +1172,19 @@
                 }
             }
 
-            if let msg = successToast {
-                Text(msg).font(.headline)
-                    .padding(.horizontal, 20).padding(.vertical, 10)
-                    .background(RoundedRectangle(cornerRadius: 10).fill(.ultraThickMaterial))
-                    .transition(.move(edge: .bottom).combined(with: .opacity))
-                    .onAppear {
-                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
-                            withAnimation { successToast = nil }
-                        }
-                    }
-            }
         }
     }
 
     private var editAppsGrid: some View {
         ScrollView {
             LazyVStack(alignment: .leading, spacing: 24) {
-                ForEach(groups) { group in
+                ForEach(editGroups) { group in
                     editFlatGroup(group)
                 }
             }
             .padding(20)
         }
+        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
     }
 
     private func editFlatGroup(_ group: TagGroup) -> some View {
@@ -1068,31 +1213,71 @@
             ) {
                 ForEach(group.apps) { app in editableAppItem(app) }
             }
+            .frame(minHeight: group.apps.isEmpty ? max(52, iconSize + 12) : 0)
         }
     }
 
     private func selectableTagItem(_ tagName: String) -> some View {
         let isSelected = selectedTagNames.contains(tagName)
+        let isRemovableCandidate = removableTagNames.contains(tagName)
+        let isEnabled = editTagOperation == .add || isRemovableCandidate
+        let showsPendingRemoval = editTagOperation == .remove && isRemovableCandidate && !isSelected
+        let isUncommon = tagName == TagDatabase.uncommonTagKey
+        let colorIndex = isUncommon ? 0 : (tagColors[tagName] ?? 0)
         return HStack(spacing: 6) {
             Circle()
-                .fill(isSelected ? Color.accentColor : Color.secondary.opacity(0.3))
+                .fill(isSelected ? Color.accentColor : Color.secondary.opacity(isEnabled ? 0.3 : 0.18))
                 .frame(width: 16, height: 16)
                 .overlay(isSelected ? Image(systemName: "checkmark").font(.system(size: 8, weight: .bold)).foregroundStyle(.white) : nil)
-            Text(tagName).font(.system(size: 13, weight: .medium)).foregroundStyle(.primary)
+            Text(displayTagName(tagName))
+                .font(.system(size: 13, weight: isUncommon ? .semibold : .medium))
+                .foregroundStyle(isEnabled ? Color.primary : Color.secondary.opacity(0.66))
+                .lineLimit(1)
+                .truncationMode(.tail)
+                .frame(maxWidth: .infinity, alignment: .leading)
+            HStack(spacing: 4) {
+                if isUncommon {
+                    Image(systemName: "questionmark.bubble.fill")
+                        .font(.system(size: 11, weight: .semibold))
+                        .foregroundStyle(isEnabled ? Color.secondary : Color.secondary.opacity(0.55))
+                        .frame(width: 16, height: 16)
+                }
+                if showsPendingRemoval {
+                    Image(systemName: "trash.fill")
+                        .font(.system(size: 11, weight: .bold))
+                        .foregroundStyle(.red)
+                        .frame(width: 16, height: 16)
+                        .transition(.scale(scale: 0.85).combined(with: .opacity))
+                } else if editTagOperation == .remove {
+                    Color.clear.frame(width: 16, height: 16)
+                }
+            }
+            .frame(
+                minWidth: editTagOperation == .remove
+                    ? (isUncommon ? 36 : 16)
+                    : (isUncommon ? 16 : 0),
+                alignment: .trailing
+            )
+            .layoutPriority(1)
         }
-        .padding(.horizontal, 10).padding(.vertical, 5)
+        .padding(.horizontal, 10)
+        .frame(height: 27)
         .frame(maxWidth: .infinity, alignment: .leading)
         .background(RoundedRectangle(cornerRadius: 6)
-            .fill(Color(nsColor: TagColor.nsColor(for: tagColors[tagName] ?? 0).withAlphaComponent(0.3))))
+            .fill(Color(nsColor: TagColor.nsColor(for: colorIndex).withAlphaComponent(isEnabled ? (isUncommon ? 0.22 : 0.3) : 0.12))))
         .contentShape(RoundedRectangle(cornerRadius: 6))
         .onTapGesture {
+            guard isEnabled else { return }
             if isSelected { selectedTagNames.remove(tagName) } else { selectedTagNames.insert(tagName) }
         }
+        .opacity(isEnabled ? 1.0 : 0.58)
+        .animation(.easeInOut(duration: 0.15), value: showsPendingRemoval)
     }
 
     private func tagReorderGesture(for tagName: String) -> some Gesture {
         DragGesture(minimumDistance: 4, coordinateSpace: .named("tagReorderList"))
             .onChanged { value in
+                guard canReorderTag(tagName) else { return }
                 if dragItem == nil {
                     dragItem = tagName
                 }
@@ -1100,6 +1285,7 @@
                 reorderDraggedTag(at: value.location)
             }
             .onEnded { _ in
+                guard canReorderTag(tagName) else { return }
                 dragItem = nil
                 TagEditor.reorderTags(draggedTagNames)
             }
@@ -1121,17 +1307,31 @@
     }
 
     private func confirmAssign() {
-        guard !selectedAppPaths.isEmpty else { return }
-        let paths = selectedAppPaths.map { $0.path }
-        let tags = sortedTagNames.filter { selectedTagNames.contains($0) }
-        TagEditor.setTags(tags, to: paths)
-        selectedAppPaths = []; selectedTagNames = []
+        let selectedApps = allApps.filter { selectedAppPaths.contains($0.path) }
+        let tags = pendingOperationTagNames
+        guard !selectedApps.isEmpty, !tags.isEmpty else { return }
+
+        let paths = selectedApps.map { $0.path.path }
+        let feedback = buildEditActionFeedback(for: selectedApps, tags: tags)
+
+        switch editTagOperation {
+        case .add:
+            TagEditor.appendTags(tags, to: paths)
+        case .remove:
+            TagEditor.removeTags(tags, from: paths)
+        }
+        selectedAppPaths = []
+        selectedTagNames = []
+        removableTagNames = []
         refreshApps()
-        withAnimation { successToast = tr("edit.success") }
+        showEditActionFeedback(feedback)
     }
 
     private func cancelEditApps() {
-        selectedAppPaths = []; selectedTagNames = []
+        selectedAppPaths = []
+        selectedTagNames = []
+        removableTagNames = []
+        editTagOperation = .add
     }
 
     private func editableAppItem(_ app: AppInfo) -> some View {
@@ -1169,20 +1369,219 @@
     }
 
     private func syncSelectedTagsFromSelectedApps() {
-        let selectedApps = allApps.filter { selectedAppPaths.contains($0.path) }
-        guard !selectedApps.isEmpty else {
+        guard !selectedAppPaths.isEmpty else {
             selectedTagNames = []
+            removableTagNames = []
             return
         }
 
-        let editableTags = Set(sortedTagNames)
-        if selectedApps.count == 1 {
-            selectedTagNames = Set(selectedApps[0].tags.filter { editableTags.contains($0) })
+        guard editTagOperation == .remove else {
+            removableTagNames = []
             return
         }
 
-        selectedTagNames = selectedApps.reduce(into: Set<String>()) { result, app in
-            result.formUnion(app.tags.filter { editableTags.contains($0) })
+        let selectedApps = allApps.filter { selectedAppPaths.contains($0.path) }
+        let autoSelectedTags = selectedApps.reduce(into: Set<String>()) { result, app in
+            result.formUnion(app.tags)
+            if app.isUncommon {
+                result.insert(TagDatabase.uncommonTagKey)
+            }
+        }
+        removableTagNames = autoSelectedTags
+        selectedTagNames = autoSelectedTags
+    }
+
+    private var operationHeaderPicker: some View {
+        HStack(spacing: 8) {
+            Text(tr("edit.operationLabel"))
+                .font(.system(size: 12, weight: .medium))
+                .foregroundStyle(.secondary)
+
+            HStack(spacing: 6) {
+                operationModeButton(.add, titleKey: "edit.modeAdd", systemImage: "plus.circle.fill")
+                operationModeButton(.remove, titleKey: "edit.modeRemove", systemImage: "minus.circle.fill")
+            }
+        }
+    }
+
+    private func operationModeButton(_ mode: EditTagOperation, titleKey: String, systemImage: String) -> some View {
+        let isActive = editTagOperation == mode
+        return Button {
+            setEditTagOperation(mode)
+        } label: {
+            Label(tr(titleKey), systemImage: systemImage)
+                .font(.system(size: 12, weight: .semibold))
+                .padding(.horizontal, 12)
+                .padding(.vertical, 6)
+                .foregroundStyle(isActive ? Color.white : Color.primary)
+                .background(
+                    Capsule(style: .continuous)
+                        .fill(isActive ? Color.accentColor : Color.secondary.opacity(0.12))
+                )
+        }
+        .buttonStyle(.plain)
+    }
+
+    private var editConfirmTitle: String {
+        switch editTagOperation {
+        case .add:
+            return tr("edit.confirmAdd")
+        case .remove:
+            return tr("edit.confirmRemove")
+        }
+    }
+
+    private func setEditTagOperation(_ mode: EditTagOperation) {
+        guard editTagOperation != mode else { return }
+        editTagOperation = mode
+        if mode == .add {
+            selectedTagNames = []
+            removableTagNames = []
+        } else {
+            syncSelectedTagsFromSelectedApps()
+        }
+    }
+
+    private var pendingOperationTagNames: [String] {
+        switch editTagOperation {
+        case .add:
+            return sortedTagNames.filter { selectedTagNames.contains($0) }
+        case .remove:
+            return sortedTagNames.filter { removableTagNames.contains($0) && !selectedTagNames.contains($0) }
+        }
+    }
+
+    private var editModeHintText: String {
+        editTagOperation == .remove ? tr("edit.removeHint") : tr("edit.modeHint")
+    }
+
+    private var headerModeHintView: some View {
+        Text(editModeHintText)
+            .font(.system(size: 12, weight: .medium))
+            .foregroundStyle(.secondary)
+            .lineLimit(1)
+            .truncationMode(.middle)
+            .frame(minWidth: 0, maxWidth: 760, alignment: .trailing)
+            .layoutPriority(1)
+            .help(editModeHintText)
+    }
+
+    private var editActionFeedbackOverlay: some View {
+        GeometryReader { proxy in
+            if let feedback = editActionFeedback {
+                ZStack {
+                    Color.black.opacity(0.08)
+                        .ignoresSafeArea()
+
+                    EditActionFeedbackBubble(
+                        title: feedback.title,
+                        message: feedback.message,
+                        onClose: dismissEditActionFeedback
+                    )
+                    .frame(width: min(760, max(360, proxy.size.width - 120)))
+                    .position(x: proxy.size.width / 2, y: proxy.size.height / 2)
+                    .transition(.scale(scale: 0.94).combined(with: .opacity))
+                }
+                .zIndex(700)
+            }
+        }
+        .ignoresSafeArea()
+        .allowsHitTesting(editActionFeedback != nil)
+    }
+
+    private func buildEditActionFeedback(for selectedApps: [AppInfo], tags: [String]) -> EditActionFeedback {
+        let appNames = selectedApps.map(\.name)
+            .joined(separator: localizedListSeparator)
+
+        switch editTagOperation {
+        case .add:
+            let actualAddCount = selectedApps.reduce(into: 0) { count, app in
+                count += tags.reduce(into: 0) { subtotal, tagName in
+                    if !appHasTag(app, tagName: tagName) {
+                        subtotal += 1
+                    }
+                }
+            }
+
+            return EditActionFeedback(
+                title: tr("edit.confirmAdd"),
+                message: formattedFeedbackMessage(
+                    forKey: "edit.feedbackAddFormat",
+                    replacements: [
+                        "%appCount%": "\(selectedApps.count)",
+                        "%appNames%": appNames,
+                        "%tagCount%": "\(actualAddCount)"
+                    ]
+                )
+            )
+
+        case .remove:
+            let actualRemovedTagNames = tags.filter { tagName in
+                selectedApps.contains(where: { appHasTag($0, tagName: tagName) })
+            }
+            let actualRemoveCount = selectedApps.reduce(into: 0) { count, app in
+                count += tags.reduce(into: 0) { subtotal, tagName in
+                    if appHasTag(app, tagName: tagName) {
+                        subtotal += 1
+                    }
+                }
+            }
+
+            return EditActionFeedback(
+                title: tr("edit.confirmRemove"),
+                message: formattedFeedbackMessage(
+                    forKey: "edit.feedbackRemoveFormat",
+                    replacements: [
+                        "%appCount%": "\(selectedApps.count)",
+                        "%appNames%": appNames,
+                        "%tagCount%": "\(actualRemoveCount)",
+                        "%tagNames%": actualRemovedTagNames
+                            .map(displayTagName)
+                            .joined(separator: localizedListSeparator)
+                    ]
+                )
+            )
+        }
+    }
+
+    private func appHasTag(_ app: AppInfo, tagName: String) -> Bool {
+        if tagName == TagDatabase.uncommonTagKey {
+            return app.isUncommon
+        }
+        return app.tags.contains(tagName)
+    }
+
+    private func formattedFeedbackMessage(forKey key: String, replacements: [String: String]) -> String {
+        replacements.reduce(tr(key)) { partial, replacement in
+            partial.replacingOccurrences(of: replacement.key, with: replacement.value)
+        }
+    }
+
+    private func showEditActionFeedback(_ feedback: EditActionFeedback) {
+        let feedbackID = feedback.id
+        withAnimation(.spring(response: 0.24, dampingFraction: 0.82)) {
+            editActionFeedback = feedback
+        }
+        DispatchQueue.main.asyncAfter(deadline: .now() + 2.6) {
+            guard editActionFeedback?.id == feedbackID else { return }
+            dismissEditActionFeedback()
+        }
+    }
+
+    private func dismissEditActionFeedback() {
+        withAnimation(.easeOut(duration: 0.18)) {
+            editActionFeedback = nil
+        }
+    }
+
+    private var localizedListSeparator: String {
+        switch L10n.currentCode {
+        case "zh-Hans", "zh-Hant", "ja":
+            return "、"
+        case "ar", "ar-Najdi":
+            return "، "
+        default:
+            return ", "
         }
     }
 
@@ -1193,7 +1592,11 @@
         // Use user-defined order, fall back to alpha
         let ordered = draggedTagNames.filter { filtered.contains($0) }
         let remaining = filtered.filter { !ordered.contains($0) }.sorted()
-        return ordered + remaining
+        return ordered + remaining + [TagDatabase.uncommonTagKey]
+    }
+
+    private func displayTagName(_ tagName: String) -> String {
+        tagName == TagDatabase.uncommonTagKey ? tr("group.uncommon") : tagName
     }
 
     private var tagLabels: [TagLabel] {
@@ -1217,7 +1620,98 @@
         }
     }
 
+    private var editGroups: [TagGroup] {
+        groups + [
+            TagGroup(
+                name: tr("group.uncommon"),
+                apps: allApps.filter(\.isUncommon).sorted {
+                    $0.name.localizedStandardCompare($1.name) == .orderedAscending
+                }
+            )
+        ]
+    }
+
     // MARK: - Actions
+
+    private func handleBubbleHover(app: AppInfo, frame: CGRect, hovering: Bool) {
+        guard editingBubble == nil else { return }
+        if hovering {
+            hoveredBubble = AppBubbleContext(app: app, frame: frame)
+        } else if hoveredBubble?.app.path == app.path {
+            hoveredBubble = nil
+        }
+    }
+
+    private func beginEditingBubbleNote(app: AppInfo, frame: CGRect) {
+        bubbleDraftNote = currentNote(for: app)
+        hoveredBubble = nil
+        editingBubble = AppBubbleContext(app: app, frame: frame)
+        DispatchQueue.main.async {
+            bubbleNoteFocused = true
+        }
+    }
+
+    private func commitBubbleNote() {
+        guard let context = editingBubble else { return }
+        let limited = String(bubbleDraftNote.prefix(TagDatabase.maxAppNoteLength))
+            .trimmingCharacters(in: .whitespacesAndNewlines)
+        TagEditor.setAppNote(limited, for: context.app.path.path)
+        bubbleDraftNote = limited
+        editingBubble = nil
+        bubbleNoteFocused = false
+        refreshApps()
+    }
+
+    private func dismissAppBubble() {
+        hoveredBubble = nil
+        editingBubble = nil
+        bubbleNoteFocused = false
+    }
+
+    private func currentNote(for app: AppInfo) -> String {
+        if let latest = allApps.first(where: { $0.path == app.path })?.note {
+            return latest
+        }
+        return app.note ?? ""
+    }
+
+    private func bubblePlacement(for frame: CGRect, rootFrame: CGRect) -> BubblePlacement {
+        frame.minY - rootFrame.minY < 170 ? .below : .above
+    }
+
+    private func bubbleMetrics(
+        for context: AppBubbleContext,
+        rootFrame: CGRect,
+        rootSize: CGSize,
+        width: CGFloat,
+        placement: BubblePlacement,
+        isEditing: Bool
+    ) -> (centerX: CGFloat, centerY: CGFloat) {
+        let appCenterX = context.frame.midX - rootFrame.minX
+        let halfWidth = width / 2
+        let centerX = min(max(appCenterX, halfWidth + 24), max(halfWidth + 24, rootSize.width - halfWidth - 24))
+        let estimatedHeight = estimatedBubbleHeight(for: context.app, width: width, isEditing: isEditing)
+        let topY: CGFloat
+        if placement == .below {
+            topY = context.frame.maxY - rootFrame.minY + 10
+        } else {
+            topY = context.frame.minY - rootFrame.minY - estimatedHeight - 10
+        }
+        let clampedTopY = min(max(topY, 14), max(14, rootSize.height - estimatedHeight - 14))
+        return (centerX, clampedTopY + estimatedHeight / 2)
+    }
+
+    private func estimatedBubbleHeight(for app: AppInfo, width: CGFloat, isEditing: Bool) -> CGFloat {
+        if isEditing {
+            return 120
+        }
+        let note = currentNote(for: app).trimmingCharacters(in: .whitespacesAndNewlines)
+        guard !note.isEmpty else { return 68 }
+        let contentWidth = max(160, width - 48)
+        let approxCharsPerLine = max(16, Int(contentWidth / 14))
+        let lines = min(4, max(1, Int(ceil(Double(note.count) / Double(approxCharsPerLine)))))
+        return 70 + CGFloat(lines) * 20
+    }
 
     func scrollTo(_ id: String) {
         withAnimation(.easeInOut(duration: 0.25)) { scrollProxy?.scrollTo(id, anchor: .top) }
@@ -1405,9 +1899,9 @@
 
     func refreshApps(forceLayoutRefresh: Bool = false) {
         DispatchQueue.global(qos: .userInitiated).async {
-            var apps = AppIndexer.scan()
-            let store = TagDatabase.load()
-            apps = TagEditor.annotate(apps: apps)
+            let scannedApps = AppIndexer.scan()
+            let store = TagEditor.reconcileScannedApps(scannedApps)
+            let apps = TagEditor.annotate(apps: scannedApps, store: store)
             let colors = store.tags.mapValues { $0.color }
             let order = TagEditor.orderedTagNames()
             DispatchQueue.main.async {
@@ -1428,6 +1922,9 @@
         appDragModeActive = false
         endTagNavReorder()
         hideOverlay()
+        DispatchQueue.global(qos: .utility).async {
+            TagEditor.recordLauncherOpen(for: app.path.path)
+        }
         let configuration = NSWorkspace.OpenConfiguration()
         NSWorkspace.shared.openApplication(at: app.path, configuration: configuration)
     }
diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 2eb3d99..eacfa1d 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -10,11 +10,13 @@
     let tags: [String]
     let bundleIdentifier: String?
     let icon: NSImage  // Pre-loaded during background scan
+    var isUncommon: Bool = false
+    var note: String? = nil
 
     /// True if this is an Apple pre-installed app.
     var isAppleApp: Bool {
         if let bid = bundleIdentifier, bid.hasPrefix("com.apple.") { return true }
-        return path.path.hasPrefix("/System/Applications/")
+        return AppIndexer.isSystemAppPath(path.path)
     }
 
     func hash(into hasher: inout Hasher) { hasher.combine(path) }
@@ -53,45 +55,95 @@
     static let searchPaths: [URL] = [
         URL(fileURLWithPath: "/Applications"),
         URL(fileURLWithPath: "/System/Applications"),
+        URL(fileURLWithPath: "/System/Cryptexes/App/System/Applications"),
+        URL(fileURLWithPath: "/System/Volumes/Preboot/Cryptexes/App/System/Applications"),
         FileManager.default.homeDirectoryForCurrentUser
             .appendingPathComponent("Applications")
     ]
 
+    private static let systemAppPathPrefixes = [
+        "/System/Applications/",
+        "/System/Cryptexes/App/System/Applications/",
+        "/System/Volumes/Preboot/Cryptexes/App/System/Applications/"
+    ]
+
+    static func isSystemAppPath(_ path: String) -> Bool {
+        systemAppPathPrefixes.contains { path.hasPrefix($0) }
+    }
+
     /// Scan all standard locations. Tags are annotated from TagDatabase by the caller.
     static func scan() -> [AppInfo] {
-        var seen = Set<URL>()
+        var seenResolvedPaths = Set<String>()
         var apps: [AppInfo] = []
 
         for baseURL in searchPaths {
+            scanDirectChildren(
+                in: baseURL,
+                apps: &apps,
+                seenResolvedPaths: &seenResolvedPaths
+            )
+
             guard let enumerator = FileManager.default.enumerator(
                 at: baseURL,
-                includingPropertiesForKeys: [.isDirectoryKey],
+                includingPropertiesForKeys: [.isDirectoryKey, .isSymbolicLinkKey],
                 options: [.skipsHiddenFiles, .skipsPackageDescendants]
             ) else { continue }
 
             for case let url as URL in enumerator {
-                guard url.pathExtension == "app" else { continue }
-                guard !seen.contains(url) else { continue }
-                seen.insert(url)
-
-                let name = url.deletingPathExtension().lastPathComponent
-                let icon = NSWorkspace.shared.icon(forFile: url.path)
-                icon.size = NSSize(width: 96, height: 96)
-                var bundleId: String? = nil
-                if let bundle = Bundle(url: url) {
-                    bundleId = bundle.bundleIdentifier
-                }
-
-                apps.append(AppInfo(
-                    name: name, path: url, tags: [],
-                    bundleIdentifier: bundleId, icon: icon
-                ))
+                appendAppIfNeeded(
+                    at: url,
+                    apps: &apps,
+                    seenResolvedPaths: &seenResolvedPaths
+                )
             }
         }
 
         return apps.sorted {
             $0.name.localizedStandardCompare($1.name) == .orderedAscending
         }
+    }
+
+    private static func scanDirectChildren(
+        in baseURL: URL,
+        apps: inout [AppInfo],
+        seenResolvedPaths: inout Set<String>
+    ) {
+        guard let names = try? FileManager.default.contentsOfDirectory(atPath: baseURL.path) else { return }
+
+        for name in names where !name.hasPrefix(".") {
+            appendAppIfNeeded(
+                at: baseURL.appendingPathComponent(name),
+                apps: &apps,
+                seenResolvedPaths: &seenResolvedPaths
+            )
+        }
+    }
+
+    private static func appendAppIfNeeded(
+        at url: URL,
+        apps: inout [AppInfo],
+        seenResolvedPaths: inout Set<String>
+    ) {
+        guard url.pathExtension.lowercased() == "app" else { return }
+
+        let displayURL = url.standardizedFileURL
+        let resolvedURL = displayURL.resolvingSymlinksInPath().standardizedFileURL
+        guard seenResolvedPaths.insert(resolvedURL.path).inserted else { return }
+
+        let name = displayURL.deletingPathExtension().lastPathComponent
+        let icon = NSWorkspace.shared.icon(forFile: displayURL.path)
+        icon.size = NSSize(width: 96, height: 96)
+
+        let bundleId = Bundle(url: displayURL)?.bundleIdentifier
+            ?? Bundle(url: resolvedURL)?.bundleIdentifier
+
+        apps.append(AppInfo(
+            name: name,
+            path: displayURL,
+            tags: [],
+            bundleIdentifier: bundleId,
+            icon: icon
+        ))
     }
 
     /// Group apps by their tags (from TagDatabase).
@@ -140,6 +192,14 @@
 }
 
 enum TagDatabase {
+    static let uncommonTagKey = "__system.uncommon"
+    static let maxAppNoteLength = 80
+    static let autoUncommonOpenThreshold = 100
+
+    enum UncommonSource: String, Codable {
+        case auto
+        case manual
+    }
 
     // MARK: Storage types
 
@@ -152,6 +212,42 @@
         var tags: [String: TagDef] = [:]
         var appTags: [String: [String]] = [:]  // path → tag names
         var tagOrder: [String] = []  // display order; empty → alpha sort
+        var uncommonAppPaths: [String] = []  // special marker; does not affect normal groups
+        var uncommonSources: [String: UncommonSource] = [:]  // current uncommon source: auto/manual
+        var appOpenCounts: [String: Int] = [:]  // launches opened from TagLauncher
+        var knownAppPaths: [String] = []  // baseline set to detect newly installed apps
+        var appNotes: [String: String] = [:]  // path → user note; retained even if marker is removed
+
+        enum CodingKeys: String, CodingKey {
+            case version
+            case tags
+            case appTags
+            case tagOrder
+            case uncommonAppPaths
+            case uncommonSources
+            case appOpenCounts
+            case knownAppPaths
+            case appNotes
+        }
+
+        init() {}
+
+        init(from decoder: Decoder) throws {
+            let container = try decoder.container(keyedBy: CodingKeys.self)
+            version = try container.decodeIfPresent(Int.self, forKey: .version) ?? 1
+            tags = try container.decodeIfPresent([String: TagDef].self, forKey: .tags) ?? [:]
+            appTags = try container.decodeIfPresent([String: [String]].self, forKey: .appTags) ?? [:]
+            tagOrder = try container.decodeIfPresent([String].self, forKey: .tagOrder) ?? []
+            uncommonAppPaths = try container.decodeIfPresent([String].self, forKey: .uncommonAppPaths) ?? []
+            uncommonSources = try container.decodeIfPresent([String: UncommonSource].self, forKey: .uncommonSources) ?? [:]
+            appOpenCounts = try container.decodeIfPresent([String: Int].self, forKey: .appOpenCounts) ?? [:]
+            knownAppPaths = try container.decodeIfPresent([String].self, forKey: .knownAppPaths) ?? []
+            appNotes = try container.decodeIfPresent([String: String].self, forKey: .appNotes) ?? [:]
+
+            for path in uncommonAppPaths where uncommonSources[path] == nil {
+                uncommonSources[path] = .manual
+            }
+        }
     }
 
     // MARK: Paths
@@ -191,7 +287,9 @@
     }
 
     static func save(_ store: Store) {
-        guard let data = try? JSONEncoder().encode(store) else { return }
+        let encoder = JSONEncoder()
+        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
+        guard let data = try? encoder.encode(store) else { return }
         try? data.write(to: storeURL, options: .atomic)
     }
 
@@ -299,12 +397,18 @@
 
     /// Annotate scanned apps with tags from the database.
     static func annotate(apps: [AppInfo]) -> [AppInfo] {
-        let store = TagDatabase.load()
+        annotate(apps: apps, store: TagDatabase.load())
+    }
+
+    static func annotate(apps: [AppInfo], store: TagDatabase.Store) -> [AppInfo] {
+        let uncommonPaths = Set(store.uncommonAppPaths)
         return apps.map { app in
             let appTags = store.appTags[app.path.path] ?? []
             return AppInfo(
                 name: app.name, path: app.path, tags: appTags,
-                bundleIdentifier: app.bundleIdentifier, icon: app.icon
+                bundleIdentifier: app.bundleIdentifier, icon: app.icon,
+                isUncommon: uncommonPaths.contains(app.path.path),
+                note: store.appNotes[app.path.path]
             )
         }
     }
@@ -333,13 +437,138 @@
     /// Replace the full editable tag set for multiple apps.
     static func setTags(_ tags: [String], to paths: [String]) {
         var store = TagDatabase.load()
+        let selectedUncommon = tags.contains(TagDatabase.uncommonTagKey)
         let validTags = tags.filter { store.tags[$0] != nil }
+        var uncommonPaths = Set(store.uncommonAppPaths)
         for path in paths {
             if validTags.isEmpty {
                 store.appTags.removeValue(forKey: path)
             } else {
                 store.appTags[path] = validTags
             }
+            if selectedUncommon {
+                uncommonPaths.insert(path)
+                store.uncommonSources[path] = .manual
+            } else {
+                uncommonPaths.remove(path)
+                store.uncommonSources.removeValue(forKey: path)
+            }
+        }
+        store.uncommonAppPaths = uncommonPaths.sorted()
+        TagDatabase.save(store)
+    }
+
+    /// Append tags to multiple apps without disturbing their existing tag sets.
+    static func appendTags(_ tags: [String], to paths: [String]) {
+        guard !tags.isEmpty, !paths.isEmpty else { return }
+
+        var store = TagDatabase.load()
+        let selectedUncommon = tags.contains(TagDatabase.uncommonTagKey)
+        let validTags = tags.filter { store.tags[$0] != nil }
+        var uncommonPaths = Set(store.uncommonAppPaths)
+
+        for path in paths {
+            var current = store.appTags[path] ?? []
+            for tag in validTags where !current.contains(tag) {
+                current.append(tag)
+            }
+            if !current.isEmpty {
+                store.appTags[path] = current
+            }
+
+            if selectedUncommon {
+                uncommonPaths.insert(path)
+                store.uncommonSources[path] = .manual
+            }
+        }
+
+        store.uncommonAppPaths = uncommonPaths.sorted()
+        TagDatabase.save(store)
+    }
+
+    /// Remove tags from multiple apps while preserving every unrelated tag.
+    static func removeTags(_ tags: [String], from paths: [String]) {
+        guard !tags.isEmpty, !paths.isEmpty else { return }
+
+        var store = TagDatabase.load()
+        let selectedUncommon = tags.contains(TagDatabase.uncommonTagKey)
+        let validTags = Set(tags.filter { store.tags[$0] != nil })
+        var uncommonPaths = Set(store.uncommonAppPaths)
+
+        for path in paths {
+            var current = store.appTags[path] ?? []
+            current.removeAll { validTags.contains($0) }
+
+            if current.isEmpty {
+                store.appTags.removeValue(forKey: path)
+            } else {
+                store.appTags[path] = current
+            }
+
+            if selectedUncommon {
+                uncommonPaths.remove(path)
+                store.uncommonSources.removeValue(forKey: path)
+            }
+        }
+
+        store.uncommonAppPaths = uncommonPaths.sorted()
+        TagDatabase.save(store)
+    }
+
+    static func reconcileScannedApps(_ apps: [AppInfo]) -> TagDatabase.Store {
+        var store = TagDatabase.load()
+        let scannedPaths = Set(apps.map { $0.path.path })
+        let knownPaths = Set(store.knownAppPaths)
+
+        if store.knownAppPaths.isEmpty {
+            store.knownAppPaths = scannedPaths.sorted()
+            if apps.isEmpty == false {
+                TagDatabase.save(store)
+            }
+            return store
+        }
+
+        let newPaths = scannedPaths.subtracting(knownPaths)
+        guard !newPaths.isEmpty else { return store }
+
+        var uncommonPaths = Set(store.uncommonAppPaths)
+        for path in newPaths {
+            uncommonPaths.insert(path)
+            store.uncommonSources[path] = .auto
+            if store.appOpenCounts[path] == nil {
+                store.appOpenCounts[path] = 0
+            }
+        }
+
+        store.uncommonAppPaths = uncommonPaths.sorted()
+        store.knownAppPaths = knownPaths.union(newPaths).sorted()
+        TagDatabase.save(store)
+        return store
+    }
+
+    static func recordLauncherOpen(for path: String) {
+        var store = TagDatabase.load()
+        store.appOpenCounts[path] = (store.appOpenCounts[path] ?? 0) + 1
+
+        if store.uncommonSources[path] == .auto,
+           store.appOpenCounts[path, default: 0] >= TagDatabase.autoUncommonOpenThreshold {
+            var uncommonPaths = Set(store.uncommonAppPaths)
+            uncommonPaths.remove(path)
+            store.uncommonAppPaths = uncommonPaths.sorted()
+            store.uncommonSources.removeValue(forKey: path)
+        }
+
+        TagDatabase.save(store)
+    }
+
+    static func setAppNote(_ note: String, for path: String) {
+        var store = TagDatabase.load()
+        let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
+        let limited = String(trimmed.prefix(TagDatabase.maxAppNoteLength))
+        if limited.isEmpty {
+            store.appNotes.removeValue(forKey: path)
+        } else {
+            store.appNotes[path] = limited
         }
         TagDatabase.save(store)
     }
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 82ab965..fe978d6 100644
--- a/Apptag/Info.plist
+++ b/Apptag/Info.plist
@@ -19,9 +19,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>5.7.8</string>
+	<string>6.0.5</string>
 	<key>CFBundleVersion</key>
-	<string>583</string>
+	<string>610</string>
 	<key>LSMinimumSystemVersion</key>
 	<string>15.0</string>
 	<key>NSHighResolutionCapable</key>
diff --git a/Apptag/Localization/ar-Najdi.json b/Apptag/Localization/ar-Najdi.json
index 3c16b47..263bb36 100644
--- a/Apptag/Localization/ar-Najdi.json
+++ b/Apptag/Localization/ar-Najdi.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "فشل التصدير",
   "settings.import": "استيراد التصنيفات والتخطيط…",
   "settings.backupDesc": "التصدير يحفظ التصنيفات والتخطيط بملف JSON. الاستيراد يستبدل الإعداد الحالي بملف.",
+  "settings.uncommonBubble": "فقاعات للتطبيقات القليلة الاستخدام",
+  "settings.uncommonBubbleDesc": "ورّ فقاعة كبيرة للاسم والملاحظة بس للتطبيقات المحددة كقليلة الاستخدام.",
   "settings.importFailed": "فشل الاستيراد",
   "edit.exit": "إنهاء التحرير",
   "edit.tags": "تحرير الوسوم",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "غير مصنف",
   "group.appleBuiltIn": "مدمج",
+  "group.uncommon": "ما ينستخدم واجد",
   "drop.systemDefaultWarning": "لا تسحب التطبيقات لتصنيفات النظام الافتراضية",
   "tag.design": "تصميم",
   "tag.development": "تطوير",
@@ -64,5 +67,22 @@
   "tag.productivity": "إنتاجية",
   "settings.coloredContainer": "حاوية ملونة",
   "settings.gridContainer": "شبكة بدون لون",
-  "settings.coloredGridContainer": "شبكة ملونة"
+  "settings.coloredGridContainer": "شبكة ملونة",
+  "appNote.edit": "عدّل ملاحظة الغرض…",
+  "appNote.placeholder": "ملاحظة غرض التطبيق",
+  "edit.modeAdd": "إضافة وسوم",
+  "edit.modeRemove": "إزالة وسوم",
+  "edit.modeHint": "علامات التحديد في اليسار تنطبق على هالعملية فقط.",
+  "edit.currentTags": "الوسوم الحالية",
+  "edit.commonTags": "الوسوم المشتركة",
+  "edit.selectionHint": "اختر التطبيقات من اليمين، ثم اختر من اليسار الوسوم اللي تبي تضيفها أو تزيلها.",
+  "edit.noAssignedTags": "التطبيق المحدد ما عليه وسوم للحين.",
+  "edit.noCommonTags": "ما فيه وسوم مشتركة بين التطبيقات المحددة.",
+  "edit.mixedTagsHint": "باقي الوسوم تختلف بين التطبيقات المحددة.",
+  "edit.operationLabel": "هالاجراء:",
+  "edit.confirmAdd": "تأكيد الإضافة",
+  "edit.confirmRemove": "تأكيد الإزالة",
+  "edit.feedbackAddFormat": "هالعملية اضافت %tagCount% من الوسوم على %appCount% تطبيقات (%appNames%).",
+  "edit.feedbackRemoveFormat": "هالعملية شالت %tagCount% من الوسوم من %appCount% تطبيقات (%appNames%). الوسوم اللي انشالت: %tagNames%.",
+  "edit.removeHint": "هنا ما تقدر تشيل الصح الا من الوسوم الموجودة من قبل. اي وسم يطلع معه رمز حذف احمر بينشال بعد التاكيد."
 }
diff --git a/Apptag/Localization/ar.json b/Apptag/Localization/ar.json
index f4f3770..1989e89 100644
--- a/Apptag/Localization/ar.json
+++ b/Apptag/Localization/ar.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "فشل التصدير",
   "settings.import": "استيراد التصنيفات والتخطيط…",
   "settings.backupDesc": "يحفظ التصدير التصنيفات والتخطيط في ملف JSON. يستبدل الاستيراد الإعداد الحالي بملف.",
+  "settings.uncommonBubble": "فقاعات التطبيقات قليلة الاستخدام",
+  "settings.uncommonBubbleDesc": "اعرض فقاعة كبيرة للاسم والملاحظة فقط للتطبيقات المحددة كقليلة الاستخدام.",
   "settings.importFailed": "فشل الاستيراد",
   "edit.exit": "إنهاء التحرير",
   "edit.tags": "تحرير الوسوم",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "غير مصنف",
   "group.appleBuiltIn": "مدمج",
+  "group.uncommon": "غير مستخدم غالبًا",
   "drop.systemDefaultWarning": "لا تسحب التطبيقات إلى التصنيفات الافتراضية للنظام",
   "tag.design": "تصميم",
   "tag.development": "تطوير",
@@ -64,5 +67,22 @@
   "tag.productivity": "إنتاجية",
   "settings.coloredContainer": "حاوية ملونة",
   "settings.gridContainer": "شبكة بلا لون",
-  "settings.coloredGridContainer": "شبكة ملونة"
+  "settings.coloredGridContainer": "شبكة ملونة",
+  "appNote.edit": "تحرير ملاحظة الغرض…",
+  "appNote.placeholder": "ملاحظة غرض التطبيق",
+  "edit.modeAdd": "إضافة وسوم",
+  "edit.modeRemove": "إزالة وسوم",
+  "edit.modeHint": "علامات التحديد على اليسار تنطبق فقط على هذه العملية.",
+  "edit.currentTags": "الوسوم الحالية",
+  "edit.commonTags": "الوسوم المشتركة",
+  "edit.selectionHint": "اختر التطبيقات من اليمين، ثم اختر من اليسار الوسوم التي تريد إضافتها أو إزالتها.",
+  "edit.noAssignedTags": "التطبيق المحدد لا يملك وسوما بعد.",
+  "edit.noCommonTags": "لا توجد وسوم مشتركة بين التطبيقات المحددة.",
+  "edit.mixedTagsHint": "الوسوم الأخرى تختلف بين التطبيقات المحددة.",
+  "edit.operationLabel": "هذا الاجراء:",
+  "edit.confirmAdd": "تأكيد الإضافة",
+  "edit.confirmRemove": "تأكيد الإزالة",
+  "edit.feedbackAddFormat": "اضافت هذه العملية %tagCount% من الوسوم عبر %appCount% تطبيقات (%appNames%).",
+  "edit.feedbackRemoveFormat": "ازالت هذه العملية %tagCount% من الوسوم من %appCount% تطبيقات (%appNames%). الوسوم التي تمت ازالتها: %tagNames%.",
+  "edit.removeHint": "هنا يمكنك الغاء تحديد الوسوم الموجودة بالفعل فقط. الوسوم التي يظهر بجانبها رمز حذف احمر ستزال بعد التاكيد."
 }
diff --git a/Apptag/Localization/cs.json b/Apptag/Localization/cs.json
index 3ed3e51..71a0a80 100644
--- a/Apptag/Localization/cs.json
+++ b/Apptag/Localization/cs.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Export se nezdařil",
   "settings.import": "Importovat kategorie a rozvržení…",
   "settings.backupDesc": "Export uloží kategorie a rozvržení do souboru JSON. Import nahradí aktuální nastavení souborem.",
+  "settings.uncommonBubble": "Bubliny pro málo používané aplikace",
+  "settings.uncommonBubbleDesc": "Zobrazí velké jméno a poznámku jen u aplikací označených jako málo používané.",
   "settings.importFailed": "Import se nezdařil",
   "edit.exit": "Ukončit úpravy",
   "edit.tags": "Upravit štítky",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Nezařazeno",
   "group.appleBuiltIn": "Vestavěné",
+  "group.uncommon": "Málo používané",
   "drop.systemDefaultWarning": "Nepřetahujte aplikace do výchozích systémových kategorií",
   "tag.design": "Design",
   "tag.development": "Vývoj",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivita",
   "settings.coloredContainer": "Barevný kontejner",
   "settings.gridContainer": "Bezbarvá mřížka",
-  "settings.coloredGridContainer": "Barevná mřížka"
+  "settings.coloredGridContainer": "Barevná mřížka",
+  "appNote.edit": "Upravit poznámku k účelu…",
+  "appNote.placeholder": "Poznámka k účelu aplikace",
+  "edit.modeAdd": "Pridat stitky",
+  "edit.modeRemove": "Odebrat stitky",
+  "edit.modeHint": "Zatrzeni vlevo plati jen pro tuto operaci.",
+  "edit.currentTags": "Aktualni stitky",
+  "edit.commonTags": "Spolecne stitky",
+  "edit.selectionHint": "Vyberte aplikace vpravo a pak vlevo zvolte stitky, ktere chcete pridat nebo odebrat.",
+  "edit.noAssignedTags": "Vybrana aplikace zatim nema zadne stitky.",
+  "edit.noCommonTags": "Vybrane aplikace nemaji zadne spolecne stitky.",
+  "edit.mixedTagsHint": "Ostatni stitky se mezi vybranymi aplikacemi lisi.",
+  "edit.operationLabel": "Akce:",
+  "edit.confirmAdd": "Potvrdit pridani",
+  "edit.confirmRemove": "Potvrdit odebrani",
+  "edit.feedbackAddFormat": "Tato akce pridala %tagCount% stitku do %appCount% aplikaci (%appNames%).",
+  "edit.feedbackRemoveFormat": "Tato akce odebrala %tagCount% stitku z %appCount% aplikaci (%appNames%). Odebrane stitky: %tagNames%.",
+  "edit.removeHint": "Zde lze odskrtnout jen jiz existujici stitky. Stitky s cervenou ikonou odstraneni budou po potvrzeni odebrany."
 }
diff --git a/Apptag/Localization/da.json b/Apptag/Localization/da.json
index 85105b8..223c133 100644
--- a/Apptag/Localization/da.json
+++ b/Apptag/Localization/da.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Eksport mislykkedes",
   "settings.import": "Importer kategorier og layout…",
   "settings.backupDesc": "Eksport gemmer kategorier og layout i en JSON-fil. Import erstatter den aktuelle opsætning med en fil.",
+  "settings.uncommonBubble": "Bobler for sjældent brugte apps",
+  "settings.uncommonBubbleDesc": "Viser stor navne- og noteboble kun for apps markeret som sjældent brugt.",
   "settings.importFailed": "Import mislykkedes",
   "edit.exit": "Afslut redigering",
   "edit.tags": "Rediger tags",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Ukategoriseret",
   "group.appleBuiltIn": "Indbygget",
+  "group.uncommon": "Sjældent brugt",
   "drop.systemDefaultWarning": "Træk ikke apps til systemets standardkategorier",
   "tag.design": "Design",
   "tag.development": "Udvikling",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivitet",
   "settings.coloredContainer": "Farvet beholder",
   "settings.gridContainer": "Farveløst gitter",
-  "settings.coloredGridContainer": "Farvet gitter"
+  "settings.coloredGridContainer": "Farvet gitter",
+  "appNote.edit": "Rediger formålsnote…",
+  "appNote.placeholder": "Formålsnote for app",
+  "edit.modeAdd": "Tilfoj tags",
+  "edit.modeRemove": "Fjern tags",
+  "edit.modeHint": "Afkrydsningerne til venstre gaelder kun for denne handling.",
+  "edit.currentTags": "Nuvaerende tags",
+  "edit.commonTags": "Faelles tags",
+  "edit.selectionHint": "Vaelg apps til hojre, og vaelg derefter til venstre de tags, du vil tilfoje eller fjerne.",
+  "edit.noAssignedTags": "Den valgte app har ingen tags endnu.",
+  "edit.noCommonTags": "De valgte apps har ingen faelles tags.",
+  "edit.mixedTagsHint": "Andre tags varierer mellem de valgte apps.",
+  "edit.operationLabel": "Handling:",
+  "edit.confirmAdd": "Bekraeft tilfojelse",
+  "edit.confirmRemove": "Bekraeft fjernelse",
+  "edit.feedbackAddFormat": "Denne handling tilfojede %tagCount% tags til %appCount% apps (%appNames%).",
+  "edit.feedbackRemoveFormat": "Denne handling fjernede %tagCount% tags fra %appCount% apps (%appNames%). Fjernede tags: %tagNames%.",
+  "edit.removeHint": "Her kan kun eksisterende tags fjernes fra markeringen. Tags med et rodt sletteikon fjernes efter bekraeftelse."
 }
diff --git a/Apptag/Localization/de.json b/Apptag/Localization/de.json
index c873c77..b603830 100644
--- a/Apptag/Localization/de.json
+++ b/Apptag/Localization/de.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Export fehlgeschlagen",
   "settings.import": "Kategorien & Layout importieren…",
   "settings.backupDesc": "Der Export speichert Kategorien und Layout als JSON-Datei. Der Import ersetzt die aktuelle Konfiguration durch eine Datei.",
+  "settings.uncommonBubble": "Hinweisblasen fuer seltene Apps",
+  "settings.uncommonBubbleDesc": "Zeigt Name und Notiz nur fuer Apps, die als selten genutzt markiert sind.",
   "settings.importFailed": "Import fehlgeschlagen",
   "edit.exit": "Bearbeitung beenden",
   "edit.tags": "Tags bearbeiten",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Nicht kategorisiert",
   "group.appleBuiltIn": "Integriert",
+  "group.uncommon": "Selten genutzt",
   "drop.systemDefaultWarning": "Ziehe keine Apps in die Standard-Systemkategorien",
   "tag.design": "Design",
   "tag.development": "Entwicklung",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivität",
   "settings.coloredContainer": "Farbiger Container",
   "settings.gridContainer": "Farbloses Raster",
-  "settings.coloredGridContainer": "Farbiges Raster"
+  "settings.coloredGridContainer": "Farbiges Raster",
+  "appNote.edit": "Verwendungsnotiz bearbeiten…",
+  "appNote.placeholder": "Verwendungsnotiz zur App",
+  "edit.modeAdd": "Tags hinzufuegen",
+  "edit.modeRemove": "Tags entfernen",
+  "edit.modeHint": "Die Haken links gelten nur fuer diesen Vorgang.",
+  "edit.currentTags": "Aktuelle Tags",
+  "edit.commonTags": "Gemeinsame Tags",
+  "edit.selectionHint": "Waehlen Sie rechts Apps aus und links die Tags, die in diesem Schritt hinzugefuegt oder entfernt werden sollen.",
+  "edit.noAssignedTags": "Die ausgewaehlte App hat noch keine Tags.",
+  "edit.noCommonTags": "Die ausgewaehlten Apps haben keine gemeinsamen Tags.",
+  "edit.mixedTagsHint": "Weitere Tags unterscheiden sich zwischen den ausgewaehlten Apps.",
+  "edit.operationLabel": "Aktion:",
+  "edit.confirmAdd": "Hinzufuegen bestaetigen",
+  "edit.confirmRemove": "Entfernen bestaetigen",
+  "edit.feedbackAddFormat": "Dieser Vorgang hat %tagCount% Tags zu %appCount% Apps hinzugefugt (%appNames%).",
+  "edit.feedbackRemoveFormat": "Dieser Vorgang hat %tagCount% Tags von %appCount% Apps entfernt (%appNames%). Entfernte Tags: %tagNames%.",
+  "edit.removeHint": "Hier konnen nur bereits vorhandene Tags abgewahlt werden. Tags mit rotem Loschsymbol werden nach der Bestatigung entfernt."
 }
diff --git a/Apptag/Localization/en.json b/Apptag/Localization/en.json
index cf572bf..61f8c53 100644
--- a/Apptag/Localization/en.json
+++ b/Apptag/Localization/en.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Export Failed",
   "settings.import": "Import Categories & Layout…",
   "settings.backupDesc": "Export saves your categories and layout to a JSON file. Import replaces the current setup with one from a file.",
+  "settings.uncommonBubble": "Uncommon app bubble tips",
+  "settings.uncommonBubbleDesc": "Show a large name and note bubble only for apps marked as Uncommon.",
   "settings.importFailed": "Import Failed",
   "edit.exit": "Exit editing",
   "edit.tags": "Edit Tags",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Uncategorized",
   "group.appleBuiltIn": "Built-in",
+  "group.uncommon": "Uncommon",
   "drop.systemDefaultWarning": "Do not drag apps into system default categories",
   "tag.design": "Design",
   "tag.development": "Development",
@@ -64,5 +67,22 @@
   "tag.productivity": "Productivity",
   "settings.coloredContainer": "Colored Container",
   "settings.gridContainer": "Colorless Grid",
-  "settings.coloredGridContainer": "Colored Grid"
+  "settings.coloredGridContainer": "Colored Grid",
+  "appNote.edit": "Edit purpose note…",
+  "appNote.placeholder": "App purpose note",
+  "edit.modeAdd": "Add tags",
+  "edit.modeRemove": "Remove tags",
+  "edit.modeHint": "Left-side checkmarks apply only to this operation.",
+  "edit.currentTags": "Current tags",
+  "edit.commonTags": "Shared tags",
+  "edit.selectionHint": "Select apps on the right, then choose the tags to add or remove on the left.",
+  "edit.noAssignedTags": "The selected app has no tags yet.",
+  "edit.noCommonTags": "These selected apps have no shared tags.",
+  "edit.mixedTagsHint": "Other tags differ between the selected apps.",
+  "edit.operationLabel": "Action:",
+  "edit.confirmAdd": "Confirm Add",
+  "edit.confirmRemove": "Confirm Remove",
+  "edit.feedbackAddFormat": "This operation added %tagCount% tags across %appCount% apps (%appNames%).",
+  "edit.feedbackRemoveFormat": "This operation removed %tagCount% tags across %appCount% apps (%appNames%). Removed tags: %tagNames%.",
+  "edit.removeHint": "Only existing tags can be unchecked here. Tags showing a red delete icon will be removed after confirmation."
 }
diff --git a/Apptag/Localization/es.json b/Apptag/Localization/es.json
index 385f2d0..3a96fbf 100644
--- a/Apptag/Localization/es.json
+++ b/Apptag/Localization/es.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Error de exportación",
   "settings.import": "Importar categorías y diseño…",
   "settings.backupDesc": "La exportación guarda las categorías y el diseño en un archivo JSON. La importación reemplaza la configuración actual.",
+  "settings.uncommonBubble": "Burbujas para apps poco usadas",
+  "settings.uncommonBubbleDesc": "Muestra una burbuja grande con nombre y nota solo para apps marcadas como poco usadas.",
   "settings.importFailed": "Error de importación",
   "edit.exit": "Salir de edición",
   "edit.tags": "Editar etiquetas",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Sin categorizar",
   "group.appleBuiltIn": "Integradas",
+  "group.uncommon": "Poco usadas",
   "drop.systemDefaultWarning": "No arrastres a las categorías predeterminadas del sistema",
   "tag.design": "Diseño",
   "tag.development": "Desarrollo",
@@ -64,5 +67,22 @@
   "tag.productivity": "Oficina",
   "settings.coloredContainer": "Contenedor coloreado",
   "settings.gridContainer": "Cuadrícula sin color",
-  "settings.coloredGridContainer": "Cuadrícula coloreada"
+  "settings.coloredGridContainer": "Cuadrícula coloreada",
+  "appNote.edit": "Editar nota de uso…",
+  "appNote.placeholder": "Nota de uso de la app",
+  "edit.modeAdd": "Anadir etiquetas",
+  "edit.modeRemove": "Quitar etiquetas",
+  "edit.modeHint": "Las marcas de la izquierda solo se aplican a esta operacion.",
+  "edit.currentTags": "Etiquetas actuales",
+  "edit.commonTags": "Etiquetas compartidas",
+  "edit.selectionHint": "Seleccione aplicaciones a la derecha y luego elija a la izquierda las etiquetas que desea anadir o quitar.",
+  "edit.noAssignedTags": "La aplicacion seleccionada aun no tiene etiquetas.",
+  "edit.noCommonTags": "Las aplicaciones seleccionadas no tienen etiquetas en comun.",
+  "edit.mixedTagsHint": "Las demas etiquetas difieren entre las aplicaciones seleccionadas.",
+  "edit.operationLabel": "Accion:",
+  "edit.confirmAdd": "Confirmar agregado",
+  "edit.confirmRemove": "Confirmar eliminacion",
+  "edit.feedbackAddFormat": "Esta operacion agrego %tagCount% etiquetas a %appCount% apps (%appNames%).",
+  "edit.feedbackRemoveFormat": "Esta operacion elimino %tagCount% etiquetas de %appCount% apps (%appNames%). Etiquetas eliminadas: %tagNames%.",
+  "edit.removeHint": "Aqui solo se pueden desmarcar etiquetas ya existentes. Las etiquetas con icono rojo de eliminacion se quitaran tras confirmar."
 }
diff --git a/Apptag/Localization/fr.json b/Apptag/Localization/fr.json
index cbe2e65..0afa8b0 100644
--- a/Apptag/Localization/fr.json
+++ b/Apptag/Localization/fr.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Échec de l'export",
   "settings.import": "Importer catégories et disposition…",
   "settings.backupDesc": "L'export sauvegarde les catégories et la disposition dans un fichier JSON. L'import remplace la configuration actuelle.",
+  "settings.uncommonBubble": "Bulles pour apps peu utilisées",
+  "settings.uncommonBubbleDesc": "Affiche une grande bulle avec le nom et une note uniquement pour les apps marquées comme peu utilisées.",
   "settings.importFailed": "Échec de l'import",
   "edit.exit": "Quitter l'édition",
   "edit.tags": "Modifier les étiquettes",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Non classé",
   "group.appleBuiltIn": "Intégrées",
+  "group.uncommon": "Peu utilisé",
   "drop.systemDefaultWarning": "Ne déposez pas dans les catégories système par défaut",
   "tag.design": "Design",
   "tag.development": "Développement",
@@ -64,5 +67,22 @@
   "tag.productivity": "Bureau",
   "settings.coloredContainer": "Conteneur coloré",
   "settings.gridContainer": "Grille sans couleur",
-  "settings.coloredGridContainer": "Grille colorée"
+  "settings.coloredGridContainer": "Grille colorée",
+  "appNote.edit": "Modifier la note d’usage…",
+  "appNote.placeholder": "Note d’usage de l’app",
+  "edit.modeAdd": "Ajouter des etiquettes",
+  "edit.modeRemove": "Retirer des etiquettes",
+  "edit.modeHint": "Les coches a gauche ne s'appliquent qu'a cette operation.",
+  "edit.currentTags": "Etiquettes actuelles",
+  "edit.commonTags": "Etiquettes communes",
+  "edit.selectionHint": "Selectionnez des apps a droite, puis choisissez a gauche les etiquettes a ajouter ou a retirer.",
+  "edit.noAssignedTags": "L'app selectionnee n'a pas encore d'etiquette.",
+  "edit.noCommonTags": "Les apps selectionnees n'ont aucune etiquette commune.",
+  "edit.mixedTagsHint": "Les autres etiquettes different selon les apps selectionnees.",
+  "edit.operationLabel": "Action :",
+  "edit.confirmAdd": "Confirmer ajout",
+  "edit.confirmRemove": "Confirmer retrait",
+  "edit.feedbackAddFormat": "Cette operation a ajoute %tagCount% etiquettes sur %appCount% apps (%appNames%).",
+  "edit.feedbackRemoveFormat": "Cette operation a supprime %tagCount% etiquettes sur %appCount% apps (%appNames%). Etiquettes retirees : %tagNames%.",
+  "edit.removeHint": "Ici, seuls les tags existants peuvent etre decoches. Les tags affichant une icone rouge de suppression seront retires apres confirmation."
 }
diff --git a/Apptag/Localization/id.json b/Apptag/Localization/id.json
index c5f5048..e3a018b 100644
--- a/Apptag/Localization/id.json
+++ b/Apptag/Localization/id.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Ekspor gagal",
   "settings.import": "Impor kategori & tata letak…",
   "settings.backupDesc": "Ekspor menyimpan kategori dan tata letak ke file JSON. Impor mengganti pengaturan saat ini dengan file.",
+  "settings.uncommonBubble": "Gelembung app jarang dipakai",
+  "settings.uncommonBubbleDesc": "Tampilkan gelembung nama besar dan catatan hanya untuk app yang ditandai jarang dipakai.",
   "settings.importFailed": "Impor gagal",
   "edit.exit": "Keluar dari pengeditan",
   "edit.tags": "Edit tag",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Tanpa kategori",
   "group.appleBuiltIn": "Bawaan",
+  "group.uncommon": "Jarang dipakai",
   "drop.systemDefaultWarning": "Jangan seret app ke kategori bawaan sistem",
   "tag.design": "Desain",
   "tag.development": "Pengembangan",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivitas",
   "settings.coloredContainer": "Kontainer berwarna",
   "settings.gridContainer": "Grid tanpa warna",
-  "settings.coloredGridContainer": "Grid berwarna"
+  "settings.coloredGridContainer": "Grid berwarna",
+  "appNote.edit": "Edit catatan kegunaan…",
+  "appNote.placeholder": "Catatan kegunaan app",
+  "edit.modeAdd": "Tambahkan label",
+  "edit.modeRemove": "Hapus label",
+  "edit.modeHint": "Centang di kiri hanya berlaku untuk operasi ini.",
+  "edit.currentTags": "Label saat ini",
+  "edit.commonTags": "Label bersama",
+  "edit.selectionHint": "Pilih app di kanan, lalu pilih di kiri label yang ingin ditambahkan atau dihapus.",
+  "edit.noAssignedTags": "App yang dipilih belum memiliki label.",
+  "edit.noCommonTags": "App yang dipilih tidak memiliki label bersama.",
+  "edit.mixedTagsHint": "Label lainnya berbeda di antara app yang dipilih.",
+  "edit.operationLabel": "Aksi:",
+  "edit.confirmAdd": "Konfirmasi tambah",
+  "edit.confirmRemove": "Konfirmasi hapus",
+  "edit.feedbackAddFormat": "Operasi ini menambahkan %tagCount% tag ke %appCount% app (%appNames%).",
+  "edit.feedbackRemoveFormat": "Operasi ini menghapus %tagCount% tag dari %appCount% app (%appNames%). Tag yang dihapus: %tagNames%.",
+  "edit.removeHint": "Di sini hanya tag yang sudah ada yang bisa dihapus centangnya. Tag dengan ikon hapus merah akan dihapus setelah konfirmasi."
 }
diff --git a/Apptag/Localization/it.json b/Apptag/Localization/it.json
index 0f20df1..c736f78 100644
--- a/Apptag/Localization/it.json
+++ b/Apptag/Localization/it.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Esportazione fallita",
   "settings.import": "Importa categorie e layout…",
   "settings.backupDesc": "L'esportazione salva categorie e layout in un file JSON. L'importazione sostituisce la configurazione attuale.",
+  "settings.uncommonBubble": "Bolle per app poco usate",
+  "settings.uncommonBubbleDesc": "Mostra una grande bolla con nome e nota solo per le app segnate come poco usate.",
   "settings.importFailed": "Importazione fallita",
   "edit.exit": "Esci dalla modifica",
   "edit.tags": "Modifica tag",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Non categorizzato",
   "group.appleBuiltIn": "Integrate",
+  "group.uncommon": "Poco usate",
   "drop.systemDefaultWarning": "Non trascinare nelle categorie di sistema predefinite",
   "tag.design": "Design",
   "tag.development": "Sviluppo",
@@ -64,5 +67,22 @@
   "tag.productivity": "Ufficio",
   "settings.coloredContainer": "Contenitore colorato",
   "settings.gridContainer": "Griglia senza colore",
-  "settings.coloredGridContainer": "Griglia colorata"
+  "settings.coloredGridContainer": "Griglia colorata",
+  "appNote.edit": "Modifica nota d’uso…",
+  "appNote.placeholder": "Nota d’uso dell’app",
+  "edit.modeAdd": "Aggiungi tag",
+  "edit.modeRemove": "Rimuovi tag",
+  "edit.modeHint": "I segni di spunta a sinistra valgono solo per questa operazione.",
+  "edit.currentTags": "Tag attuali",
+  "edit.commonTags": "Tag condivisi",
+  "edit.selectionHint": "Seleziona le app a destra, poi scegli a sinistra i tag da aggiungere o rimuovere.",
+  "edit.noAssignedTags": "L'app selezionata non ha ancora tag.",
+  "edit.noCommonTags": "Le app selezionate non hanno tag in comune.",
+  "edit.mixedTagsHint": "Gli altri tag variano tra le app selezionate.",
+  "edit.operationLabel": "Azione:",
+  "edit.confirmAdd": "Conferma aggiunta",
+  "edit.confirmRemove": "Conferma rimozione",
+  "edit.feedbackAddFormat": "Questa operazione ha aggiunto %tagCount% tag a %appCount% app (%appNames%).",
+  "edit.feedbackRemoveFormat": "Questa operazione ha rimosso %tagCount% tag da %appCount% app (%appNames%). Tag rimossi: %tagNames%.",
+  "edit.removeHint": "Qui puoi deselezionare solo i tag gia esistenti. I tag che mostrano un'icona rossa di eliminazione verranno rimossi dopo la conferma."
 }
diff --git a/Apptag/Localization/ja.json b/Apptag/Localization/ja.json
index 146eb1d..0ab1749 100644
--- a/Apptag/Localization/ja.json
+++ b/Apptag/Localization/ja.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "エクスポート失敗",
   "settings.import": "カテゴリとレイアウトをインポート…",
   "settings.backupDesc": "エクスポートはカテゴリとレイアウトをJSONファイルに保存します。インポートは現在の設定をファイルの内容で置き換えます。",
+  "settings.uncommonBubble": "あまり使わないアプリの吹き出し",
+  "settings.uncommonBubbleDesc": "あまり使わないとマークしたアプリだけに、大きな名前と用途メモを表示します。",
   "settings.importFailed": "インポート失敗",
   "edit.exit": "編集を終了",
   "edit.tags": "タグを編集",
@@ -54,6 +56,7 @@
   "app.build": "ビルド",
   "group.uncategorized": "未分類",
   "group.appleBuiltIn": "Mac内蔵",
+  "group.uncommon": "あまり使わない",
   "drop.systemDefaultWarning": "システム既定カテゴリにはドラッグできません",
   "tag.design": "デザイン",
   "tag.development": "プログラミング",
@@ -64,5 +67,22 @@
   "tag.productivity": "オフィス",
   "settings.coloredContainer": "カラーコンテナ",
   "settings.gridContainer": "無色グリッド",
-  "settings.coloredGridContainer": "カラーグリッド"
+  "settings.coloredGridContainer": "カラーグリッド",
+  "appNote.edit": "用途メモを編集…",
+  "appNote.placeholder": "アプリの用途メモ",
+  "edit.modeAdd": "タグを追加",
+  "edit.modeRemove": "タグを削除",
+  "edit.modeHint": "左側のチェックは今回の操作にのみ適用されます。",
+  "edit.currentTags": "現在のタグ",
+  "edit.commonTags": "共通タグ",
+  "edit.selectionHint": "右側でアプリを選択し、左側で今回追加または削除するタグを選んでください。",
+  "edit.noAssignedTags": "選択したアプリにはまだタグがありません。",
+  "edit.noCommonTags": "選択したアプリに共通するタグはありません。",
+  "edit.mixedTagsHint": "そのほかのタグは選択したアプリごとに異なります。",
+  "edit.operationLabel": "今回の操作:",
+  "edit.confirmAdd": "追加を確認",
+  "edit.confirmRemove": "削除を確認",
+  "edit.feedbackAddFormat": "今回の操作で、%appCount%個のアプリ(%appNames%)に%tagCount%個のタグを追加しました。",
+  "edit.feedbackRemoveFormat": "今回の操作で、%appCount%個のアプリ(%appNames%)から%tagCount%個のタグを削除しました。削除したタグ: %tagNames%。",
+  "edit.removeHint": "ここでは既存のタグだけチェックを外せます。赤い削除アイコンが表示されたタグは、確認後に削除されます。"
 }
diff --git a/Apptag/Localization/ko.json b/Apptag/Localization/ko.json
index 0159799..a6696a1 100644
--- a/Apptag/Localization/ko.json
+++ b/Apptag/Localization/ko.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "내보내기 실패",
   "settings.import": "분류와 레이아웃 가져오기…",
   "settings.backupDesc": "내보내기는 분류와 레이아웃을 JSON 파일로 저장합니다. 가져오기는 파일의 내용으로 현재 설정을 대체합니다.",
+  "settings.uncommonBubble": "자주 쓰지 않는 앱 말풍선",
+  "settings.uncommonBubbleDesc": "자주 쓰지 않음으로 표시한 앱에만 큰 이름과 메모 말풍선을 표시합니다.",
   "settings.importFailed": "가져오기 실패",
   "edit.exit": "편집 종료",
   "edit.tags": "태그 편집",
@@ -54,6 +56,7 @@
   "app.build": "빌드",
   "group.uncategorized": "미분류",
   "group.appleBuiltIn": "Mac 내장",
+  "group.uncommon": "자주 쓰지 않음",
   "drop.systemDefaultWarning": "시스템 기본 분류로 드래그하지 마세요",
   "tag.design": "디자인",
   "tag.development": "프로그래밍",
@@ -64,5 +67,22 @@
   "tag.productivity": "오피스",
   "settings.coloredContainer": "색상 컨테이너",
   "settings.gridContainer": "무색 그리드",
-  "settings.coloredGridContainer": "색상 그리드"
+  "settings.coloredGridContainer": "색상 그리드",
+  "appNote.edit": "용도 메모 편집…",
+  "appNote.placeholder": "앱 용도 메모",
+  "edit.modeAdd": "태그 추가",
+  "edit.modeRemove": "태그 제거",
+  "edit.modeHint": "왼쪽 체크는 이번 작업에만 적용됩니다.",
+  "edit.currentTags": "현재 태그",
+  "edit.commonTags": "공통 태그",
+  "edit.selectionHint": "오른쪽에서 앱을 선택한 뒤, 왼쪽에서 이번에 추가하거나 제거할 태그를 고르세요.",
+  "edit.noAssignedTags": "선택한 앱에는 아직 태그가 없습니다.",
+  "edit.noCommonTags": "선택한 앱들 사이에 공통 태그가 없습니다.",
+  "edit.mixedTagsHint": "다른 태그는 선택한 앱마다 서로 다릅니다.",
+  "edit.operationLabel": "이번 작업:",
+  "edit.confirmAdd": "추가 확인",
+  "edit.confirmRemove": "제거 확인",
+  "edit.feedbackAddFormat": "이번 작업으로 %appCount%개 앱(%appNames%)에 태그 %tagCount%개가 새로 추가되었습니다.",
+  "edit.feedbackRemoveFormat": "이번 작업으로 %appCount%개 앱(%appNames%)에서 태그 %tagCount%개가 제거되었습니다. 제거된 태그: %tagNames%.",
+  "edit.removeHint": "여기서는 이미 있는 태그만 체크 해제할 수 있습니다. 빨간 삭제 아이콘이 보이는 태그는 확인 후 제거됩니다."
 }
diff --git a/Apptag/Localization/ms.json b/Apptag/Localization/ms.json
index d547d15..1a94997 100644
--- a/Apptag/Localization/ms.json
+++ b/Apptag/Localization/ms.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Ekspor gagal",
   "settings.import": "Import kategori & susun atur…",
   "settings.backupDesc": "Eksport menyimpan kategori dan susun atur ke fail JSON. Import menggantikan tetapan semasa dengan fail.",
+  "settings.uncommonBubble": "Gelembung app jarang digunakan",
+  "settings.uncommonBubbleDesc": "Tunjukkan gelembung nama besar dan nota hanya untuk app yang ditanda jarang digunakan.",
   "settings.importFailed": "Impor gagal",
   "edit.exit": "Keluar dari pengeditan",
   "edit.tags": "Edit tag",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Tidak berkategori",
   "group.appleBuiltIn": "Terbina dalam",
+  "group.uncommon": "Jarang digunakan",
   "drop.systemDefaultWarning": "Jangan seret app ke kategori bawaan sistem",
   "tag.design": "Desain",
   "tag.development": "Pengembangan",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivitas",
   "settings.coloredContainer": "Kontainer berwarna",
   "settings.gridContainer": "Grid tanpa warna",
-  "settings.coloredGridContainer": "Grid berwarna"
+  "settings.coloredGridContainer": "Grid berwarna",
+  "appNote.edit": "Edit nota kegunaan…",
+  "appNote.placeholder": "Nota kegunaan app",
+  "edit.modeAdd": "Tambah tag",
+  "edit.modeRemove": "Buang tag",
+  "edit.modeHint": "Tanda di sebelah kiri hanya digunakan untuk operasi ini.",
+  "edit.currentTags": "Tag semasa",
+  "edit.commonTags": "Tag bersama",
+  "edit.selectionHint": "Pilih app di sebelah kanan, kemudian pilih di sebelah kiri tag yang mahu ditambah atau dibuang.",
+  "edit.noAssignedTags": "App yang dipilih belum mempunyai tag.",
+  "edit.noCommonTags": "App yang dipilih tidak mempunyai tag bersama.",
+  "edit.mixedTagsHint": "Tag lain berbeza antara app yang dipilih.",
+  "edit.operationLabel": "Tindakan:",
+  "edit.confirmAdd": "Sahkan tambah",
+  "edit.confirmRemove": "Sahkan buang",
+  "edit.feedbackAddFormat": "Operasi ini menambah %tagCount% tag pada %appCount% app (%appNames%).",
+  "edit.feedbackRemoveFormat": "Operasi ini membuang %tagCount% tag daripada %appCount% app (%appNames%). Tag yang dibuang: %tagNames%.",
+  "edit.removeHint": "Di sini hanya tag yang sedia ada boleh dinyah tanda. Tag yang memaparkan ikon padam merah akan dibuang selepas pengesahan."
 }
diff --git a/Apptag/Localization/nb.json b/Apptag/Localization/nb.json
index 02c1e47..149c38b 100644
--- a/Apptag/Localization/nb.json
+++ b/Apptag/Localization/nb.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Eksport mislyktes",
   "settings.import": "Importer kategorier og layout…",
   "settings.backupDesc": "Eksport lagrer kategorier og layout i en JSON-fil. Import erstatter gjeldende oppsett med en fil.",
+  "settings.uncommonBubble": "Bobler for lite brukte apper",
+  "settings.uncommonBubbleDesc": "Viser stor navn- og notatboble bare for apper merket som lite brukt.",
   "settings.importFailed": "Import mislyktes",
   "edit.exit": "Avslutt redigering",
   "edit.tags": "Rediger etiketter",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Ukategorisert",
   "group.appleBuiltIn": "Innebygd",
+  "group.uncommon": "Lite brukt",
   "drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
   "tag.design": "Design",
   "tag.development": "Utvikling",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivitet",
   "settings.coloredContainer": "Farget beholder",
   "settings.gridContainer": "Fargeløst rutenett",
-  "settings.coloredGridContainer": "Farget rutenett"
+  "settings.coloredGridContainer": "Farget rutenett",
+  "appNote.edit": "Rediger bruksnotat…",
+  "appNote.placeholder": "Bruksnotat for app",
+  "edit.modeAdd": "Legg til tagger",
+  "edit.modeRemove": "Fjern tagger",
+  "edit.modeHint": "Avkrysningene til venstre gjelder bare for denne handlingen.",
+  "edit.currentTags": "Gjeldende tagger",
+  "edit.commonTags": "Felles tagger",
+  "edit.selectionHint": "Velg apper til hoyre, og velg deretter til venstre hvilke tagger som skal legges til eller fjernes.",
+  "edit.noAssignedTags": "Den valgte appen har ingen tagger ennå.",
+  "edit.noCommonTags": "De valgte appene har ingen felles tagger.",
+  "edit.mixedTagsHint": "Andre tagger varierer mellom de valgte appene.",
+  "edit.operationLabel": "Handling:",
+  "edit.confirmAdd": "Bekreft legg til",
+  "edit.confirmRemove": "Bekreft fjern",
+  "edit.feedbackAddFormat": "Denne handlingen la til %tagCount% tagger i %appCount% apper (%appNames%).",
+  "edit.feedbackRemoveFormat": "Denne handlingen fjernet %tagCount% tagger fra %appCount% apper (%appNames%). Fjernede tagger: %tagNames%.",
+  "edit.removeHint": "Her kan bare eksisterende tagger fjernes fra avkrysningen. Tagger med rodt sletteikon blir fjernet etter bekreftelse."
 }
diff --git a/Apptag/Localization/nl.json b/Apptag/Localization/nl.json
index 2c1fcf8..8fec88c 100644
--- a/Apptag/Localization/nl.json
+++ b/Apptag/Localization/nl.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Export mislukt",
   "settings.import": "Categorieën en lay-out importeren…",
   "settings.backupDesc": "Export slaat categorieën en lay-out op in een JSON-bestand. Import vervangt de huidige instelling door een bestand.",
+  "settings.uncommonBubble": "Bubbels voor weinig gebruikte apps",
+  "settings.uncommonBubbleDesc": "Toont een grote naam- en notitiebubbel alleen voor apps die als weinig gebruikt zijn gemarkeerd.",
   "settings.importFailed": "Import mislukt",
   "edit.exit": "Bewerken afsluiten",
   "edit.tags": "Tags bewerken",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Niet gecategoriseerd",
   "group.appleBuiltIn": "Ingebouwd",
+  "group.uncommon": "Weinig gebruikt",
   "drop.systemDefaultWarning": "Sleep apps niet naar standaard systeemcategorieën",
   "tag.design": "Ontwerp",
   "tag.development": "Ontwikkeling",
@@ -64,5 +67,22 @@
   "tag.productivity": "Productiviteit",
   "settings.coloredContainer": "Gekleurde container",
   "settings.gridContainer": "Kleurloos raster",
-  "settings.coloredGridContainer": "Gekleurd raster"
+  "settings.coloredGridContainer": "Gekleurd raster",
+  "appNote.edit": "Gebruiksnotitie bewerken…",
+  "appNote.placeholder": "Gebruiksnotitie voor app",
+  "edit.modeAdd": "Tags toevoegen",
+  "edit.modeRemove": "Tags verwijderen",
+  "edit.modeHint": "De vinkjes links gelden alleen voor deze bewerking.",
+  "edit.currentTags": "Huidige tags",
+  "edit.commonTags": "Gedeelde tags",
+  "edit.selectionHint": "Selecteer rechts apps en kies links de tags die je wilt toevoegen of verwijderen.",
+  "edit.noAssignedTags": "De geselecteerde app heeft nog geen tags.",
+  "edit.noCommonTags": "De geselecteerde apps hebben geen gedeelde tags.",
+  "edit.mixedTagsHint": "Andere tags verschillen per geselecteerde app.",
+  "edit.operationLabel": "Actie:",
+  "edit.confirmAdd": "Toevoegen bevestigen",
+  "edit.confirmRemove": "Verwijderen bevestigen",
+  "edit.feedbackAddFormat": "Deze actie heeft %tagCount% tags toegevoegd aan %appCount% apps (%appNames%).",
+  "edit.feedbackRemoveFormat": "Deze actie heeft %tagCount% tags verwijderd van %appCount% apps (%appNames%). Verwijderde tags: %tagNames%.",
+  "edit.removeHint": "Hier kun je alleen bestaande tags uitvinken. Tags met een rood verwijderpictogram worden na bevestiging verwijderd."
 }
diff --git a/Apptag/Localization/nn.json b/Apptag/Localization/nn.json
index 155bf42..da9202c 100644
--- a/Apptag/Localization/nn.json
+++ b/Apptag/Localization/nn.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Eksport mislyktes",
   "settings.import": "Importer kategorier og layout…",
   "settings.backupDesc": "Eksport lagrer kategorier og layout i en JSON-fil. Import erstatter gjeldende oppsett med en fil.",
+  "settings.uncommonBubble": "Bobler for lite brukte appar",
+  "settings.uncommonBubbleDesc": "Viser stor namn- og notatboble berre for appar merkte som lite brukt.",
   "settings.importFailed": "Import mislyktes",
   "edit.exit": "Avslutt redigering",
   "edit.tags": "Rediger etiketter",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Ukategorisert",
   "group.appleBuiltIn": "Innebygd",
+  "group.uncommon": "Lite brukt",
   "drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
   "tag.design": "Design",
   "tag.development": "Utvikling",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivitet",
   "settings.coloredContainer": "Farget beholder",
   "settings.gridContainer": "Fargeløst rutenett",
-  "settings.coloredGridContainer": "Farget rutenett"
+  "settings.coloredGridContainer": "Farget rutenett",
+  "appNote.edit": "Rediger bruksnotat…",
+  "appNote.placeholder": "Bruksnotat for app",
+  "edit.modeAdd": "Legg til taggar",
+  "edit.modeRemove": "Fjern taggar",
+  "edit.modeHint": "Avkryssingane til venstre gjeld berre for denne handlinga.",
+  "edit.currentTags": "Noverande taggar",
+  "edit.commonTags": "Felles taggar",
+  "edit.selectionHint": "Vel appar til hogre, og vel deretter til venstre kva taggar som skal leggjast til eller fjernast.",
+  "edit.noAssignedTags": "Den valde appen har ingen taggar enno.",
+  "edit.noCommonTags": "Dei valde appane har ingen felles taggar.",
+  "edit.mixedTagsHint": "Andre taggar varierer mellom dei valde appane.",
+  "edit.operationLabel": "Handling:",
+  "edit.confirmAdd": "Stadfest legg til",
+  "edit.confirmRemove": "Stadfest fjern",
+  "edit.feedbackAddFormat": "Denne handlinga la til %tagCount% taggar i %appCount% appar (%appNames%).",
+  "edit.feedbackRemoveFormat": "Denne handlinga fjerna %tagCount% taggar fra %appCount% appar (%appNames%). Fjerna taggar: %tagNames%.",
+  "edit.removeHint": "Her kan berre eksisterande taggar fjernast frå avkryssinga. Taggar med raudt sletteikon blir fjerna etter stadfesting."
 }
diff --git a/Apptag/Localization/no.json b/Apptag/Localization/no.json
index 02c1e47..149c38b 100644
--- a/Apptag/Localization/no.json
+++ b/Apptag/Localization/no.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Eksport mislyktes",
   "settings.import": "Importer kategorier og layout…",
   "settings.backupDesc": "Eksport lagrer kategorier og layout i en JSON-fil. Import erstatter gjeldende oppsett med en fil.",
+  "settings.uncommonBubble": "Bobler for lite brukte apper",
+  "settings.uncommonBubbleDesc": "Viser stor navn- og notatboble bare for apper merket som lite brukt.",
   "settings.importFailed": "Import mislyktes",
   "edit.exit": "Avslutt redigering",
   "edit.tags": "Rediger etiketter",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Ukategorisert",
   "group.appleBuiltIn": "Innebygd",
+  "group.uncommon": "Lite brukt",
   "drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
   "tag.design": "Design",
   "tag.development": "Utvikling",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivitet",
   "settings.coloredContainer": "Farget beholder",
   "settings.gridContainer": "Fargeløst rutenett",
-  "settings.coloredGridContainer": "Farget rutenett"
+  "settings.coloredGridContainer": "Farget rutenett",
+  "appNote.edit": "Rediger bruksnotat…",
+  "appNote.placeholder": "Bruksnotat for app",
+  "edit.modeAdd": "Legg til tagger",
+  "edit.modeRemove": "Fjern tagger",
+  "edit.modeHint": "Avkrysningene til venstre gjelder bare for denne handlingen.",
+  "edit.currentTags": "Gjeldende tagger",
+  "edit.commonTags": "Felles tagger",
+  "edit.selectionHint": "Velg apper til hoyre, og velg deretter til venstre hvilke tagger som skal legges til eller fjernes.",
+  "edit.noAssignedTags": "Den valgte appen har ingen tagger ennå.",
+  "edit.noCommonTags": "De valgte appene har ingen felles tagger.",
+  "edit.mixedTagsHint": "Andre tagger varierer mellom de valgte appene.",
+  "edit.operationLabel": "Handling:",
+  "edit.confirmAdd": "Bekreft legg til",
+  "edit.confirmRemove": "Bekreft fjern",
+  "edit.feedbackAddFormat": "Denne handlingen la til %tagCount% tagger i %appCount% apper (%appNames%).",
+  "edit.feedbackRemoveFormat": "Denne handlingen fjernet %tagCount% tagger fra %appCount% apper (%appNames%). Fjernede tagger: %tagNames%.",
+  "edit.removeHint": "Her kan bare eksisterende tagger fjernes fra avkrysningen. Tagger med rodt sletteikon blir fjernet etter bekreftelse."
 }
diff --git a/Apptag/Localization/pl.json b/Apptag/Localization/pl.json
index de7c8f6..f293449 100644
--- a/Apptag/Localization/pl.json
+++ b/Apptag/Localization/pl.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Eksport nieudany",
   "settings.import": "Importuj kategorie i układ…",
   "settings.backupDesc": "Eksport zapisuje kategorie i układ do pliku JSON. Import zastępuje bieżącą konfigurację plikiem.",
+  "settings.uncommonBubble": "Dymki dla rzadko używanych aplikacji",
+  "settings.uncommonBubbleDesc": "Pokazuje dużą nazwę i notatkę tylko dla aplikacji oznaczonych jako rzadko używane.",
   "settings.importFailed": "Import nieudany",
   "edit.exit": "Zakończ edycję",
   "edit.tags": "Edytuj tagi",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Bez kategorii",
   "group.appleBuiltIn": "Wbudowane",
+  "group.uncommon": "Rzadko używane",
   "drop.systemDefaultWarning": "Nie przeciągaj aplikacji do domyślnych kategorii systemu",
   "tag.design": "Projektowanie",
   "tag.development": "Programowanie",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktywność",
   "settings.coloredContainer": "Kolorowy kontener",
   "settings.gridContainer": "Bezbarwna siatka",
-  "settings.coloredGridContainer": "Kolorowa siatka"
+  "settings.coloredGridContainer": "Kolorowa siatka",
+  "appNote.edit": "Edytuj notatkę o przeznaczeniu…",
+  "appNote.placeholder": "Notatka o przeznaczeniu aplikacji",
+  "edit.modeAdd": "Dodaj tagi",
+  "edit.modeRemove": "Usun tagi",
+  "edit.modeHint": "Zaznaczenia po lewej dotycza tylko tej operacji.",
+  "edit.currentTags": "Biezace tagi",
+  "edit.commonTags": "Wspolne tagi",
+  "edit.selectionHint": "Wybierz aplikacje po prawej, a nastepnie po lewej wybierz tagi do dodania lub usuniecia.",
+  "edit.noAssignedTags": "Wybrana aplikacja nie ma jeszcze tagow.",
+  "edit.noCommonTags": "Wybrane aplikacje nie maja wspolnych tagow.",
+  "edit.mixedTagsHint": "Pozostale tagi roznia sie miedzy wybranymi aplikacjami.",
+  "edit.operationLabel": "Dzialanie:",
+  "edit.confirmAdd": "Potwierdz dodanie",
+  "edit.confirmRemove": "Potwierdz usuniecie",
+  "edit.feedbackAddFormat": "Ta operacja dodala %tagCount% tagow do %appCount% aplikacji (%appNames%).",
+  "edit.feedbackRemoveFormat": "Ta operacja usunela %tagCount% tagow z %appCount% aplikacji (%appNames%). Usuniete tagi: %tagNames%.",
+  "edit.removeHint": "Tutaj mozna odznaczyc tylko istniejace tagi. Tagi z czerwona ikona usuniecia zostana usuniete po potwierdzeniu."
 }
diff --git a/Apptag/Localization/pt-BR.json b/Apptag/Localization/pt-BR.json
index 35f43e9..24501ef 100644
--- a/Apptag/Localization/pt-BR.json
+++ b/Apptag/Localization/pt-BR.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Falha ao exportar",
   "settings.import": "Importar categorias e layout…",
   "settings.backupDesc": "A exportação salva categorias e layout em um arquivo JSON. A importação substitui a configuração atual por um arquivo.",
+  "settings.uncommonBubble": "Balões para apps pouco usados",
+  "settings.uncommonBubbleDesc": "Mostra um balão grande com nome e nota apenas para apps marcados como pouco usados.",
   "settings.importFailed": "Falha ao importar",
   "edit.exit": "Sair da edição",
   "edit.tags": "Editar etiquetas",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Sem categoria",
   "group.appleBuiltIn": "Integrados",
+  "group.uncommon": "Pouco usados",
   "drop.systemDefaultWarning": "Não arraste apps para categorias padrão do sistema",
   "tag.design": "Design",
   "tag.development": "Desenvolvimento",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produtividade",
   "settings.coloredContainer": "Contêiner colorido",
   "settings.gridContainer": "Grade sem cor",
-  "settings.coloredGridContainer": "Grade colorida"
+  "settings.coloredGridContainer": "Grade colorida",
+  "appNote.edit": "Editar nota de uso…",
+  "appNote.placeholder": "Nota de uso do app",
+  "edit.modeAdd": "Adicionar tags",
+  "edit.modeRemove": "Remover tags",
+  "edit.modeHint": "As marcacoes a esquerda valem apenas para esta operacao.",
+  "edit.currentTags": "Tags atuais",
+  "edit.commonTags": "Tags em comum",
+  "edit.selectionHint": "Selecione apps a direita e depois escolha a esquerda as tags para adicionar ou remover.",
+  "edit.noAssignedTags": "O app selecionado ainda nao tem tags.",
+  "edit.noCommonTags": "Os apps selecionados nao tem tags em comum.",
+  "edit.mixedTagsHint": "As outras tags diferem entre os apps selecionados.",
+  "edit.operationLabel": "Acao:",
+  "edit.confirmAdd": "Confirmar adicao",
+  "edit.confirmRemove": "Confirmar remocao",
+  "edit.feedbackAddFormat": "Esta operacao adicionou %tagCount% tags em %appCount% apps (%appNames%).",
+  "edit.feedbackRemoveFormat": "Esta operacao removeu %tagCount% tags de %appCount% apps (%appNames%). Tags removidas: %tagNames%.",
+  "edit.removeHint": "Aqui so e possivel desmarcar tags ja existentes. As tags com icone vermelha de remocao serao removidas apos a confirmacao."
 }
diff --git a/Apptag/Localization/ro.json b/Apptag/Localization/ro.json
index 6f2fa9f..4b099ff 100644
--- a/Apptag/Localization/ro.json
+++ b/Apptag/Localization/ro.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Export eșuat",
   "settings.import": "Importă categorii și aspect…",
   "settings.backupDesc": "Exportul salvează categoriile și aspectul într-un fișier JSON. Importul înlocuiește configurația curentă cu un fișier.",
+  "settings.uncommonBubble": "Bule pentru aplicații folosite rar",
+  "settings.uncommonBubbleDesc": "Afișează o bulă mare cu nume și notă doar pentru aplicațiile marcate ca folosite rar.",
   "settings.importFailed": "Import eșuat",
   "edit.exit": "Ieși din editare",
   "edit.tags": "Editează etichete",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Necategorizat",
   "group.appleBuiltIn": "Integrat",
+  "group.uncommon": "Folosit rar",
   "drop.systemDefaultWarning": "Nu trage aplicații în categoriile implicite ale sistemului",
   "tag.design": "Design",
   "tag.development": "Dezvoltare",
@@ -64,5 +67,22 @@
   "tag.productivity": "Productivitate",
   "settings.coloredContainer": "Container colorat",
   "settings.gridContainer": "Grilă fără culoare",
-  "settings.coloredGridContainer": "Grilă colorată"
+  "settings.coloredGridContainer": "Grilă colorată",
+  "appNote.edit": "Editează nota de utilizare…",
+  "appNote.placeholder": "Notă de utilizare a aplicației",
+  "edit.modeAdd": "Adauga etichete",
+  "edit.modeRemove": "Elimina etichete",
+  "edit.modeHint": "Bifele din stanga se aplica doar acestei operatii.",
+  "edit.currentTags": "Etichete curente",
+  "edit.commonTags": "Etichete comune",
+  "edit.selectionHint": "Selecteaza aplicatii in dreapta, apoi alege in stanga etichetele pe care vrei sa le adaugi sau sa le elimini.",
+  "edit.noAssignedTags": "Aplicatia selectata nu are inca etichete.",
+  "edit.noCommonTags": "Aplicatiile selectate nu au etichete comune.",
+  "edit.mixedTagsHint": "Celelalte etichete difera intre aplicatiile selectate.",
+  "edit.operationLabel": "Actiune:",
+  "edit.confirmAdd": "Confirma adaugarea",
+  "edit.confirmRemove": "Confirma eliminarea",
+  "edit.feedbackAddFormat": "Aceasta actiune a adaugat %tagCount% etichete in %appCount% aplicatii (%appNames%).",
+  "edit.feedbackRemoveFormat": "Aceasta actiune a eliminat %tagCount% etichete din %appCount% aplicatii (%appNames%). Etichete eliminate: %tagNames%.",
+  "edit.removeHint": "Aici pot fi debifate doar etichetele deja existente. Etichetele care afiseaza o pictograma rosie de stergere vor fi eliminate dupa confirmare."
 }
diff --git a/Apptag/Localization/ru.json b/Apptag/Localization/ru.json
index 71801d2..ac7856a 100644
--- a/Apptag/Localization/ru.json
+++ b/Apptag/Localization/ru.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Ошибка экспорта",
   "settings.import": "Импорт категорий и макета…",
   "settings.backupDesc": "Экспорт сохраняет категории и макет в JSON-файл. Импорт заменяет текущие настройки данными из файла.",
+  "settings.uncommonBubble": "Подсказки для редких приложений",
+  "settings.uncommonBubbleDesc": "Показывает большое имя и заметку только для приложений, отмеченных как редко используемые.",
   "settings.importFailed": "Ошибка импорта",
   "edit.exit": "Выйти из редактирования",
   "edit.tags": "Редактировать теги",
@@ -54,6 +56,7 @@
   "app.build": "Сборка",
   "group.uncategorized": "Без категории",
   "group.appleBuiltIn": "Встроенные",
+  "group.uncommon": "Редко используется",
   "drop.systemDefaultWarning": "Не перетаскивайте в системные категории по умолчанию",
   "tag.design": "Дизайн",
   "tag.development": "Разработка",
@@ -64,5 +67,22 @@
   "tag.productivity": "Офис",
   "settings.coloredContainer": "Цветной контейнер",
   "settings.gridContainer": "Бесцветная сетка",
-  "settings.coloredGridContainer": "Цветная сетка"
+  "settings.coloredGridContainer": "Цветная сетка",
+  "appNote.edit": "Изменить заметку о назначении…",
+  "appNote.placeholder": "Заметка о назначении приложения",
+  "edit.modeAdd": "Добавить теги",
+  "edit.modeRemove": "Удалить теги",
+  "edit.modeHint": "Отметки слева применяются только к этой операции.",
+  "edit.currentTags": "Текущие теги",
+  "edit.commonTags": "Общие теги",
+  "edit.selectionHint": "Выберите приложения справа, затем слева отметьте теги, которые нужно добавить или удалить.",
+  "edit.noAssignedTags": "У выбранного приложения пока нет тегов.",
+  "edit.noCommonTags": "У выбранных приложений нет общих тегов.",
+  "edit.mixedTagsHint": "Остальные теги различаются у выбранных приложений.",
+  "edit.operationLabel": "Действие:",
+  "edit.confirmAdd": "Подтвердить добавление",
+  "edit.confirmRemove": "Подтвердить удаление",
+  "edit.feedbackAddFormat": "Эта операция добавила %tagCount% тегов для %appCount% приложений (%appNames%).",
+  "edit.feedbackRemoveFormat": "Эта операция удалила %tagCount% тегов у %appCount% приложений (%appNames%). Удаленные теги: %tagNames%.",
+  "edit.removeHint": "Здесь можно снять отметку только с уже существующих тегов. Теги с красным значком удаления будут удалены после подтверждения."
 }
diff --git a/Apptag/Localization/sr-Cyrl.json b/Apptag/Localization/sr-Cyrl.json
index b5a1238..2016fec 100644
--- a/Apptag/Localization/sr-Cyrl.json
+++ b/Apptag/Localization/sr-Cyrl.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Извоз није успео",
   "settings.import": "Увези категорије и распоред…",
   "settings.backupDesc": "Извоз чува категорије и распоред у JSON датотеку. Увоз замењује тренутна подешавања датотеком.",
+  "settings.uncommonBubble": "Облачићи за ретке апликације",
+  "settings.uncommonBubbleDesc": "Приказује велико име и белешку само за апликације означене као ретко коришћене.",
   "settings.importFailed": "Увоз није успео",
   "edit.exit": "Изађи из уређивања",
   "edit.tags": "Уреди ознаке",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Некатегорисано",
   "group.appleBuiltIn": "Уграђено",
+  "group.uncommon": "Ретко коришћено",
   "drop.systemDefaultWarning": "Не превлачите апликације у подразумеване системске категорије",
   "tag.design": "Дизајн",
   "tag.development": "Развој",
@@ -64,5 +67,22 @@
   "tag.productivity": "Продуктивност",
   "settings.coloredContainer": "Обојени контејнер",
   "settings.gridContainer": "Безбојна мрежа",
-  "settings.coloredGridContainer": "Обојена мрежа"
+  "settings.coloredGridContainer": "Обојена мрежа",
+  "appNote.edit": "Уреди белешку о намени…",
+  "appNote.placeholder": "Белешка о намени апликације",
+  "edit.modeAdd": "Додај ознаке",
+  "edit.modeRemove": "Уклони ознаке",
+  "edit.modeHint": "Означавање лево важи само за ову радњу.",
+  "edit.currentTags": "Тренутне ознаке",
+  "edit.commonTags": "Заједничке ознаке",
+  "edit.selectionHint": "Изаберите апликације десно, па лево изаберите ознаке које желите да додате или уклоните.",
+  "edit.noAssignedTags": "Изабрана апликација још нема ознаке.",
+  "edit.noCommonTags": "Изабране апликације немају заједничке ознаке.",
+  "edit.mixedTagsHint": "Остале ознаке се разликују између изабраних апликација.",
+  "edit.operationLabel": "Радња:",
+  "edit.confirmAdd": "Потврди додавање",
+  "edit.confirmRemove": "Потврди уклањање",
+  "edit.feedbackAddFormat": "Ова радња је додала %tagCount% ознака за %appCount% апликација (%appNames%).",
+  "edit.feedbackRemoveFormat": "Ова радња је уклонила %tagCount% ознака са %appCount% апликација (%appNames%). Уклоњене ознаке: %tagNames%.",
+  "edit.removeHint": "Овде можете одчекирати само постојеће ознаке. Ознаке са црвеном иконицом брисања биће уклоњене након потврде."
 }
diff --git a/Apptag/Localization/sv.json b/Apptag/Localization/sv.json
index 13ab7f0..044d869 100644
--- a/Apptag/Localization/sv.json
+++ b/Apptag/Localization/sv.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Export misslyckades",
   "settings.import": "Importera kategorier och layout…",
   "settings.backupDesc": "Export sparar kategorier och layout till en JSON-fil. Import ersätter den aktuella konfigurationen med en fil.",
+  "settings.uncommonBubble": "Bubblor för sällan använda appar",
+  "settings.uncommonBubbleDesc": "Visar en stor namn- och anteckningsbubbla endast för appar markerade som sällan använda.",
   "settings.importFailed": "Import misslyckades",
   "edit.exit": "Avsluta redigering",
   "edit.tags": "Redigera taggar",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Okategoriserat",
   "group.appleBuiltIn": "Inbyggt",
+  "group.uncommon": "Sällan använd",
   "drop.systemDefaultWarning": "Dra inte appar till systemets standardkategorier",
   "tag.design": "Design",
   "tag.development": "Utveckling",
@@ -64,5 +67,22 @@
   "tag.productivity": "Produktivitet",
   "settings.coloredContainer": "Färgad behållare",
   "settings.gridContainer": "Färglöst rutnät",
-  "settings.coloredGridContainer": "Färgat rutnät"
+  "settings.coloredGridContainer": "Färgat rutnät",
+  "appNote.edit": "Redigera användningsanteckning…",
+  "appNote.placeholder": "Användningsanteckning för app",
+  "edit.modeAdd": "Lagg till taggar",
+  "edit.modeRemove": "Ta bort taggar",
+  "edit.modeHint": "Markeringarna till vanster galler bara for den har atgarden.",
+  "edit.currentTags": "Nuvarande taggar",
+  "edit.commonTags": "Gemensamma taggar",
+  "edit.selectionHint": "Valj appar till hoger och valj sedan till vanster vilka taggar som ska laggas till eller tas bort.",
+  "edit.noAssignedTags": "Den valda appen har inga taggar an.",
+  "edit.noCommonTags": "De valda apparna har inga gemensamma taggar.",
+  "edit.mixedTagsHint": "Ovriga taggar skiljer sig mellan de valda apparna.",
+  "edit.operationLabel": "Atgard:",
+  "edit.confirmAdd": "Bekrafta lagg till",
+  "edit.confirmRemove": "Bekrafta ta bort",
+  "edit.feedbackAddFormat": "Den har atgarden lade till %tagCount% taggar i %appCount% appar (%appNames%).",
+  "edit.feedbackRemoveFormat": "Den har atgarden tog bort %tagCount% taggar fran %appCount% appar (%appNames%). Borttagna taggar: %tagNames%.",
+  "edit.removeHint": "Har kan bara befintliga taggar avmarkeras. Taggar med en rod raderingsikon tas bort efter bekraftelse."
 }
diff --git a/Apptag/Localization/th.json b/Apptag/Localization/th.json
index 05274ad..63b72c8 100644
--- a/Apptag/Localization/th.json
+++ b/Apptag/Localization/th.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "ส่งออกไม่สำเร็จ",
   "settings.import": "นำเข้าหมวดหมู่และเลย์เอาต์…",
   "settings.backupDesc": "การส่งออกจะบันทึกหมวดหมู่และเลย์เอาต์เป็นไฟล์ JSON การนำเข้าจะแทนที่การตั้งค่าปัจจุบันด้วยไฟล์",
+  "settings.uncommonBubble": "บับเบิลสำหรับแอปที่ไม่ค่อยใช้",
+  "settings.uncommonBubbleDesc": "แสดงชื่อขนาดใหญ่และบันทึกเฉพาะแอปที่ทำเครื่องหมายว่าไม่ค่อยใช้",
   "settings.importFailed": "นำเข้าไม่สำเร็จ",
   "edit.exit": "ออกจากการแก้ไข",
   "edit.tags": "แก้ไขแท็ก",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "ไม่จัดหมวดหมู่",
   "group.appleBuiltIn": "ในตัว",
+  "group.uncommon": "ไม่ค่อยใช้",
   "drop.systemDefaultWarning": "อย่าลากแอปไปยังหมวดหมู่เริ่มต้นของระบบ",
   "tag.design": "ออกแบบ",
   "tag.development": "พัฒนา",
@@ -64,5 +67,22 @@
   "tag.productivity": "งาน",
   "settings.coloredContainer": "คอนเทนเนอร์มีสี",
   "settings.gridContainer": "กริดไม่มีสี",
-  "settings.coloredGridContainer": "กริดมีสี"
+  "settings.coloredGridContainer": "กริดมีสี",
+  "appNote.edit": "แก้ไขบันทึกการใช้งาน…",
+  "appNote.placeholder": "บันทึกการใช้งานแอป",
+  "edit.modeAdd": "เพิ่มแท็ก",
+  "edit.modeRemove": "ลบแท็ก",
+  "edit.modeHint": "เครื่องหมายทางซ้ายมีผลกับการทำงานครั้งนี้เท่านั้น",
+  "edit.currentTags": "แท็กปัจจุบัน",
+  "edit.commonTags": "แท็กร่วม",
+  "edit.selectionHint": "เลือกแอปทางขวา แล้วเลือกทางซ้ายว่าแท็กใดจะเพิ่มหรือลบในการทำครั้งนี้",
+  "edit.noAssignedTags": "แอปที่เลือกยังไม่มีแท็ก",
+  "edit.noCommonTags": "แอปที่เลือกไม่มีแท็กร่วมกัน",
+  "edit.mixedTagsHint": "แท็กอื่น ๆ ของแอปที่เลือกยังแตกต่างกัน",
+  "edit.operationLabel": "การทำงานนี้:",
+  "edit.confirmAdd": "ยืนยันการเพิ่ม",
+  "edit.confirmRemove": "ยืนยันการลบ",
+  "edit.feedbackAddFormat": "การทำงานครั้งนี้ได้เพิ่มแท็ก %tagCount% รายการให้กับแอป %appCount% แอป (%appNames%).",
+  "edit.feedbackRemoveFormat": "การทำงานครั้งนี้ได้ลบแท็ก %tagCount% รายการออกจากแอป %appCount% แอป (%appNames%). แท็กที่ลบ: %tagNames%.",
+  "edit.removeHint": "ที่นี่สามารถยกเลิกเครื่องหมายได้เฉพาะแท็กที่มีอยู่แล้วเท่านั้น แท็กที่แสดงไอคอนลบสีแดงจะถูกลบหลังจากยืนยัน"
 }
diff --git a/Apptag/Localization/tr.json b/Apptag/Localization/tr.json
index 0226153..c483666 100644
--- a/Apptag/Localization/tr.json
+++ b/Apptag/Localization/tr.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Dışa aktarma başarısız",
   "settings.import": "Kategorileri ve düzeni içe aktar…",
   "settings.backupDesc": "Dışa aktarma kategorileri ve düzeni JSON dosyasına kaydeder. İçe aktarma mevcut kurulumu dosyadakiyle değiştirir.",
+  "settings.uncommonBubble": "Az kullanılan app balonları",
+  "settings.uncommonBubbleDesc": "Yalnızca az kullanılan olarak işaretlenen app’lerde büyük ad ve not balonu gösterir.",
   "settings.importFailed": "İçe aktarma başarısız",
   "edit.exit": "Düzenlemeden çık",
   "edit.tags": "Etiketleri düzenle",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Kategorisiz",
   "group.appleBuiltIn": "Yerleşik",
+  "group.uncommon": "Az kullanılan",
   "drop.systemDefaultWarning": "Uygulamaları sistem varsayılan kategorilerine sürüklemeyin",
   "tag.design": "Tasarım",
   "tag.development": "Geliştirme",
@@ -64,5 +67,22 @@
   "tag.productivity": "Verimlilik",
   "settings.coloredContainer": "Renkli Kapsayıcı",
   "settings.gridContainer": "Renksiz Izgara",
-  "settings.coloredGridContainer": "Renkli Izgara"
+  "settings.coloredGridContainer": "Renkli Izgara",
+  "appNote.edit": "Kullanım notunu düzenle…",
+  "appNote.placeholder": "App kullanım notu",
+  "edit.modeAdd": "Etiket ekle",
+  "edit.modeRemove": "Etiket kaldir",
+  "edit.modeHint": "Soldaki isaretler yalnizca bu islem icin gecerlidir.",
+  "edit.currentTags": "Guncel etiketler",
+  "edit.commonTags": "Ortak etiketler",
+  "edit.selectionHint": "Saga taraftan uygulamalari secin, sonra soldan eklemek veya kaldirmak istediginiz etiketleri secin.",
+  "edit.noAssignedTags": "Secilen uygulamanin henuz etiketi yok.",
+  "edit.noCommonTags": "Secilen uygulamalarin ortak etiketi yok.",
+  "edit.mixedTagsHint": "Diger etiketler secilen uygulamalar arasinda farklilik gosterir.",
+  "edit.operationLabel": "Islem:",
+  "edit.confirmAdd": "Eklemeyi onayla",
+  "edit.confirmRemove": "Kaldirmayi onayla",
+  "edit.feedbackAddFormat": "Bu islem, %appCount% uygulamada (%appNames%) %tagCount% etiket ekledi.",
+  "edit.feedbackRemoveFormat": "Bu islem, %appCount% uygulamadan (%appNames%) %tagCount% etiketi kaldirdi. Kaldirilan etiketler: %tagNames%.",
+  "edit.removeHint": "Burada sadece mevcut etiketlerin isareti kaldirilabilir. Kirmizi silme simgesi gorunen etiketler onaydan sonra kaldirilir."
 }
diff --git a/Apptag/Localization/uk.json b/Apptag/Localization/uk.json
index d11f823..d59916c 100644
--- a/Apptag/Localization/uk.json
+++ b/Apptag/Localization/uk.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Помилка експорту",
   "settings.import": "Імпорт категорій і макета…",
   "settings.backupDesc": "Експорт зберігає категорії та макет у JSON-файл. Імпорт замінює поточні налаштування даними з файлу.",
+  "settings.uncommonBubble": "Підказки для рідкісних програм",
+  "settings.uncommonBubbleDesc": "Показує велику назву й нотатку лише для програм, позначених як рідко використовувані.",
   "settings.importFailed": "Помилка імпорту",
   "edit.exit": "Вийти з редагування",
   "edit.tags": "Редагувати теги",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Без категорії",
   "group.appleBuiltIn": "Вбудовані",
+  "group.uncommon": "Рідко використовується",
   "drop.systemDefaultWarning": "Не перетягуйте застосунки в системні категорії за замовчуванням",
   "tag.design": "Дизайн",
   "tag.development": "Розробка",
@@ -64,5 +67,22 @@
   "tag.productivity": "Продуктивність",
   "settings.coloredContainer": "Кольоровий контейнер",
   "settings.gridContainer": "Безбарвна сітка",
-  "settings.coloredGridContainer": "Кольорова сітка"
+  "settings.coloredGridContainer": "Кольорова сітка",
+  "appNote.edit": "Редагувати нотатку про призначення…",
+  "appNote.placeholder": "Нотатка про призначення програми",
+  "edit.modeAdd": "Додати теги",
+  "edit.modeRemove": "Вилучити теги",
+  "edit.modeHint": "Позначки ліворуч застосовуються лише до цієї дії.",
+  "edit.currentTags": "Поточні теги",
+  "edit.commonTags": "Спільні теги",
+  "edit.selectionHint": "Виберіть застосунки праворуч, а потім ліворуч позначте теги, які треба додати або вилучити.",
+  "edit.noAssignedTags": "У вибраного застосунку ще немає тегів.",
+  "edit.noCommonTags": "У вибраних застосунків немає спільних тегів.",
+  "edit.mixedTagsHint": "Інші теги відрізняються між вибраними застосунками.",
+  "edit.operationLabel": "Дія:",
+  "edit.confirmAdd": "Підтвердити додавання",
+  "edit.confirmRemove": "Підтвердити вилучення",
+  "edit.feedbackAddFormat": "Ця дія додала %tagCount% тегів для %appCount% застосунків (%appNames%).",
+  "edit.feedbackRemoveFormat": "Ця дія вилучила %tagCount% тегів у %appCount% застосунків (%appNames%). Вилучені теги: %tagNames%.",
+  "edit.removeHint": "Тут можна зняти позначку лише з наявних тегів. Теги з червоною іконкою видалення буде вилучено після підтвердження."
 }
diff --git a/Apptag/Localization/vi.json b/Apptag/Localization/vi.json
index f4add4a..77caf61 100644
--- a/Apptag/Localization/vi.json
+++ b/Apptag/Localization/vi.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "Xuất thất bại",
   "settings.import": "Nhập danh mục & bố cục…",
   "settings.backupDesc": "Xuất sẽ lưu danh mục và bố cục thành tệp JSON. Nhập sẽ thay thế thiết lập hiện tại bằng một tệp.",
+  "settings.uncommonBubble": "Bong bóng cho app ít dùng",
+  "settings.uncommonBubbleDesc": "Chỉ hiển thị bong bóng tên lớn và ghi chú cho app được đánh dấu là ít dùng.",
   "settings.importFailed": "Nhập thất bại",
   "edit.exit": "Thoát chỉnh sửa",
   "edit.tags": "Sửa thẻ",
@@ -54,6 +56,7 @@
   "app.build": "Build",
   "group.uncategorized": "Chưa phân loại",
   "group.appleBuiltIn": "Tích hợp",
+  "group.uncommon": "Ít dùng",
   "drop.systemDefaultWarning": "Không kéo ứng dụng vào danh mục mặc định của hệ thống",
   "tag.design": "Thiết kế",
   "tag.development": "Phát triển",
@@ -64,5 +67,22 @@
   "tag.productivity": "Năng suất",
   "settings.coloredContainer": "Vùng chứa có màu",
   "settings.gridContainer": "Lưới không màu",
-  "settings.coloredGridContainer": "Lưới có màu"
+  "settings.coloredGridContainer": "Lưới có màu",
+  "appNote.edit": "Sửa ghi chú mục đích…",
+  "appNote.placeholder": "Ghi chú mục đích của app",
+  "edit.modeAdd": "Them nhan",
+  "edit.modeRemove": "Xoa nhan",
+  "edit.modeHint": "Cac dau tich ben trai chi ap dung cho thao tac nay.",
+  "edit.currentTags": "Nhan hien tai",
+  "edit.commonTags": "Nhan chung",
+  "edit.selectionHint": "Chon app o ben phai, sau do chon o ben trai cac nhan can them hoac xoa.",
+  "edit.noAssignedTags": "App da chon chua co nhan nao.",
+  "edit.noCommonTags": "Cac app da chon khong co nhan chung.",
+  "edit.mixedTagsHint": "Cac nhan khac khac nhau giua cac app da chon.",
+  "edit.operationLabel": "Tac vu nay:",
+  "edit.confirmAdd": "Xac nhan them",
+  "edit.confirmRemove": "Xac nhan xoa",
+  "edit.feedbackAddFormat": "Lan nay da them %tagCount% the cho %appCount% ung dung (%appNames%).",
+  "edit.feedbackRemoveFormat": "Lan nay da go %tagCount% the khoi %appCount% ung dung (%appNames%). The da go: %tagNames%.",
+  "edit.removeHint": "Tai day chi co the bo chon cac the da ton tai. Cac the hien bieu tuong xoa mau do se bi go sau khi xac nhan."
 }
diff --git a/Apptag/Localization/zh-Hans.json b/Apptag/Localization/zh-Hans.json
index 9134421..fe7722a 100644
--- a/Apptag/Localization/zh-Hans.json
+++ b/Apptag/Localization/zh-Hans.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "导出失败",
   "settings.import": "导入分类与布局…",
   "settings.backupDesc": "导出将分类与布局保存为 JSON 文件。导入用一个文件替换当前设置。",
+  "settings.uncommonBubble": "不常用 App 气泡提示",
+  "settings.uncommonBubbleDesc": "仅对标记为“不常用”的 App 显示大号名称与用途备忘。",
   "settings.importFailed": "导入失败",
   "edit.exit": "退出编辑",
   "edit.tags": "编辑标签",
@@ -54,6 +56,7 @@
   "app.build": "构建",
   "group.uncategorized": "未分类",
   "group.appleBuiltIn": "Mac自带",
+  "group.uncommon": "不常用",
   "drop.systemDefaultWarning": "请勿拖动至系统默认分类",
   "tag.design": "设计",
   "tag.development": "编程",
@@ -64,5 +67,22 @@
   "tag.productivity": "办公",
   "settings.coloredContainer": "彩色容器",
   "settings.gridContainer": "无色网格",
-  "settings.coloredGridContainer": "彩色网格"
+  "settings.coloredGridContainer": "彩色网格",
+  "appNote.edit": "编辑用途备忘…",
+  "appNote.placeholder": "APP 用途备忘",
+  "edit.modeAdd": "添加标签",
+  "edit.modeRemove": "移除标签",
+  "edit.modeHint": "左侧勾选只作用于本次操作。",
+  "edit.currentTags": "当前标签",
+  "edit.commonTags": "共同标签",
+  "edit.selectionHint": "先在右侧选择应用,再在左侧勾选本次要添加或移除的标签。",
+  "edit.noAssignedTags": "当前选中的应用还没有标签。",
+  "edit.noCommonTags": "当前选中的这些应用没有共同标签。",
+  "edit.mixedTagsHint": "这些应用的其他标签彼此不同。",
+  "edit.operationLabel": "本轮操作:",
+  "edit.confirmAdd": "确认添加",
+  "edit.confirmRemove": "确认移除",
+  "edit.feedbackAddFormat": "本次针对%appCount%个应用(%appNames%)新增了%tagCount%个标签。",
+  "edit.feedbackRemoveFormat": "本次针对%appCount%个应用(%appNames%)移除了%tagCount%个标签(%tagNames%)。",
+  "edit.removeHint": "仅可取消已有标签的勾选;出现红色删除图标的标签会在确认后被移除。"
 }
diff --git a/Apptag/Localization/zh-Hant.json b/Apptag/Localization/zh-Hant.json
index 3258d52..48d347f 100644
--- a/Apptag/Localization/zh-Hant.json
+++ b/Apptag/Localization/zh-Hant.json
@@ -33,6 +33,8 @@
   "settings.exportFailed": "匯出失敗",
   "settings.import": "匯入分類與版面配置…",
   "settings.backupDesc": "匯出將分類與版面配置儲存為 JSON 檔案。匯入用一個檔案取代目前設定。",
+  "settings.uncommonBubble": "不常用 App 氣泡提示",
+  "settings.uncommonBubbleDesc": "僅對標記為「不常用」的 App 顯示大號名稱與用途備忘。",
   "settings.importFailed": "匯入失敗",
   "edit.exit": "結束編輯",
   "edit.tags": "編輯標籤",
@@ -54,6 +56,7 @@
   "app.build": "建置",
   "group.uncategorized": "未分類",
   "group.appleBuiltIn": "Mac內建",
+  "group.uncommon": "不常用",
   "drop.systemDefaultWarning": "請勿拖動至系統預設分類",
   "tag.design": "設計",
   "tag.development": "程式設計",
@@ -64,5 +67,22 @@
   "tag.productivity": "辦公",
   "settings.coloredContainer": "彩色容器",
   "settings.gridContainer": "無色網格",
-  "settings.coloredGridContainer": "彩色網格"
+  "settings.coloredGridContainer": "彩色網格",
+  "appNote.edit": "編輯用途備忘…",
+  "appNote.placeholder": "APP 用途備忘",
+  "edit.modeAdd": "新增標籤",
+  "edit.modeRemove": "移除標籤",
+  "edit.modeHint": "左側勾選只作用於本次操作。",
+  "edit.currentTags": "目前標籤",
+  "edit.commonTags": "共同標籤",
+  "edit.selectionHint": "先在右側選取應用,再在左側勾選本次要新增或移除的標籤。",
+  "edit.noAssignedTags": "目前選取的應用尚未有標籤。",
+  "edit.noCommonTags": "目前選取的這些應用沒有共同標籤。",
+  "edit.mixedTagsHint": "這些應用的其他標籤彼此不同。",
+  "edit.operationLabel": "本輪操作:",
+  "edit.confirmAdd": "確認新增",
+  "edit.confirmRemove": "確認移除",
+  "edit.feedbackAddFormat": "本次針對%appCount%個應用(%appNames%)新增了%tagCount%個標籤。",
+  "edit.feedbackRemoveFormat": "本次針對%appCount%個應用(%appNames%)移除了%tagCount%個標籤(%tagNames%)。",
+  "edit.removeHint": "僅可取消既有標籤的勾選;出現紅色刪除圖示的標籤會在確認後被移除。"
 }
diff --git a/Apptag/TagGroupView.swift b/Apptag/TagGroupView.swift
index 249db2d..de24653 100644
--- a/Apptag/TagGroupView.swift
+++ b/Apptag/TagGroupView.swift
@@ -11,6 +11,8 @@
     var showNames: Bool = true
     var dragModeActive: Bool = false
     var onDragModeChange: ((Bool) -> Void)? = nil
+    var onBubbleHover: ((AppInfo, CGRect, Bool) -> Void)? = nil
+    var onEditNote: ((AppInfo, CGRect) -> Void)? = nil
     var onDropApp: ((String, String, Bool) -> Void)? = nil
 
     /// Adaptive columns — auto-fit based on icon size and available width.
@@ -53,6 +55,8 @@
                         sourceTag: group.name,
                         dragModeActive: dragModeActive,
                         onDragModeChange: onDragModeChange,
+                        onBubbleHover: onBubbleHover,
+                        onEditNote: onEditNote,
                         onSelect: { onSelectApp(app) }
                     )
                 }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 194c64b..873b152 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
 # Apptag Changelog
 
+## [6.0.5] — 2026-05-16
+
+- 修正应用分类编辑页的批量操作逻辑:添加模式只追加手动勾选的标签,移除模式会先自动勾选右侧已选 App 现有的标签合集,未命中的标签置灰不可点
+- 移除模式下,取消某个已有标签的勾选后,会在标签右侧显示鲜红删除图标;重新勾回后,删除图标立即消失
+- 优化移除模式的左侧提示文案,明确说明只有已有标签可取消勾选,出现红色删除图标的标签会在确认后被移除
+- 新增居中全屏反馈提示:点击「确认添加 / 确认移除」后,会复用“不常用 App”气泡矩形样式显示本次操作结果,并支持自动消失和右上角关闭
+- 反馈统计改为真实增减计数:逐个 App 计算实际新增/移除的标签关系数,避免把原本已存在或不存在的标签重复计入
+- 调整编辑页顶部排版:将本轮操作说明移到确认按钮同一行,并为长文案提供省略与悬浮完整提示;左侧“选择标签 / 拖动排序”提示改为与标签列表左边缘对齐
+- 将英文标题文案由 `This edit:` 收敛为更自然的 `Action:`,并同步检查、收紧其他语言的对应表述
+- 修复编辑分类页左侧 sidebar 被说明文字横向撑开的布局问题,固定左栏宽度并恢复右侧应用区紧贴分隔线展开
+- 左侧 `Select tags / Drag to reorder` 提示现在严格约束在标签列表宽度内,超长时换行,不再越出左侧标签列表边界
+- 去掉编辑分类页顶部居中的标题,把空间让给右侧模式说明;右侧说明现在会优先尽量完整显示,只有左右按钮区确实排不下时才从中间省略
+- 修复顶部模式说明使用无限宽度参与窗口约束计算时可能触发 AppKit 约束更新异常并闪退的问题
+- 修复新版 macOS 上 Safari 等系统 App 可能被扫描遗漏的问题:扫描器现在会识别搜索目录的直接 `.app` 子级、补充 Cryptex 系统应用目录,并用真实路径去重但保留稳定入口路径
+- 补齐编辑模式相关新增文案到全部语言文件,避免切换语言后出现缺失键
+- 版本号更新为 `6.0.5`,Build 更新为 `610`
+
+## [6.0.0] — 2026-05-16
+
+- 新增“不常用”特殊系统标记:只在 App 分类编辑模式中显示,不作为普通分类出现在日常应用列表中
+- “不常用”不改变 App 原有分类归属;App 仍留在原分类中,只额外获得低频标记
+- 新增“不常用 App 气泡提示”开关,默认开启,放在设置页“数据”页签最上方
+- 对已标记为“不常用”的 App,在开关开启后 hover 显示大号 App 名称 Bubble;有用途备注时同步显示备注
+- 支持右键“不常用”App 编辑用途备忘,备注最多 80 个 Unicode 字符
+- 备注数据进入分类与布局 JSON,并与“不常用”标记状态解耦:移除“不常用”标记后备注仍保留
+- 编辑模式中的批量标签操作改为“添加标签 / 移除标签”双模式;右侧选中 App 不再自动反向勾选左侧标签,避免批量污染原有分类
+- 为编辑模式新增只读标签状态区:单选显示当前标签,多选显示共同标签,并提示其他标签可能不同
+- 新扫描到的 App 会自动标记为“不常用”,并开始统计从 TagLauncher 打开的次数;达到 100 次后,只有系统自动加上的“不常用”才会被自动移除
+- 为新增功能补齐全部 29 个语言版本文案
+- 版本号更新为 `6.0.0`,Build 更新为 `600`
+
 ## [5.7.8] — 2026-05-15
 
 - 修复 TagLauncher 浮层每次打开都会重复添加 Escape 键本地监听的问题,关闭浮层时会同步释放监听器,避免事件监听累积

--
Gitblit v1.9.3