From 7a5b90ce9aff96fa46f356c8feb66a4a02e8f218 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 27 Jun 2026 01:18:07 +0800
Subject: [PATCH] Fix bottom pinned app grid containers

---
 src/Apptag/AppGridCollectionView.swift |   34 ++++++++++++++++++++++++++++++++--
 src/CHANGELOG.md                       |    7 +++++++
 src/Apptag/Info.plist                  |    4 ++--
 CODEGRAPH.md                           |    4 ++--
 4 files changed, 43 insertions(+), 6 deletions(-)

diff --git a/CODEGRAPH.md b/CODEGRAPH.md
index d6b92c7..791e669 100644
--- a/CODEGRAPH.md
+++ b/CODEGRAPH.md
@@ -1,6 +1,6 @@
 # TagLauncher CODEGRAPH
 
-最后更新:2026-06-26
+最后更新:2026-06-27
 
 用途:本文件是 TagLauncher 的长期工程地图。修改用户可见行为、状态模型、构建/发布入口、核心源码或 QA 入口前,应先读本文件,再读指向的真实源码。若本文件与源码不一致,以源码为准,并在同一任务内更新本文件。
 
@@ -45,7 +45,7 @@
   - `AppGridIconNSView`:应用图标单元、点击、hover 气泡、拖拽起点。
   - 底部使用技巧是原生 AppKit overlay,不允许改成 SwiftUI;必须拦截自身点击区域。
   - 不拥有 AppGrid 全屏渐变背景,只接收 theme 推导出的玻璃和文本可读性 token。
-  - 样式 2 / 样式 3 的瀑布流容器布局会把 Mac 自带容器固定到底部并横向占满。
+  - 样式 2 / 样式 3 的瀑布流容器布局会把未分类和 Mac 自带作为底部特殊容器:未分类倒数第二、Mac 自带最后,二者横向占满;普通列末尾会补齐到特殊容器上沿,避免底部参差空隙。
 - `src/Apptag/AppGridTheme.swift`
   - AppGrid 主题事实源:theme id、localization key、设置页 swatch、全屏渐变背景、玻璃明暗、编辑模式 token。
   - 持久化 key:`appGridThemeID`。
diff --git a/src/Apptag/AppGridCollectionView.swift b/src/Apptag/AppGridCollectionView.swift
index 8e95bc8..8807590 100644
--- a/src/Apptag/AppGridCollectionView.swift
+++ b/src/Apptag/AppGridCollectionView.swift
@@ -1777,7 +1777,7 @@
 
     private struct LayoutItem {
         let index: Int
-        let frame: NSRect
+        var frame: NSRect
     }
 
     private struct LayoutPlan {
@@ -1913,7 +1913,15 @@
         let columnWidth = floor((availableWidth - gap * CGFloat(columnCount - 1)) / CGFloat(columnCount))
         var columnHeights = Array(repeating: outerPadding, count: columnCount)
         var items: [LayoutItem] = []
-        let bottomPinnedGroups = groups.enumerated().filter { $0.element.containerID == AppContainerID.appleBuiltIn }
+        var lastItemIndexForColumn = Array<Int?>(repeating: nil, count: columnCount)
+        let bottomPinnedGroups = groups.enumerated()
+            .filter { bottomPinnedPriority(for: $0.element.containerID) != nil }
+            .sorted {
+                let lhsPriority = bottomPinnedPriority(for: $0.element.containerID) ?? Int.max
+                let rhsPriority = bottomPinnedPriority(for: $1.element.containerID) ?? Int.max
+                if lhsPriority != rhsPriority { return lhsPriority < rhsPriority }
+                return $0.offset < $1.offset
+            }
         let bottomPinnedIndexes = Set(bottomPinnedGroups.map { $0.offset })
 
         for (index, group) in groups.enumerated() {
@@ -1933,10 +1941,21 @@
                     frame: NSRect(x: x, y: y, width: columnWidth, height: height)
                 )
             )
+            lastItemIndexForColumn[columnIndex] = items.count - 1
             columnHeights[columnIndex] = y + height + gap
         }
 
         var contentBottom = columnHeights.max() ?? outerPadding
+        if !bottomPinnedGroups.isEmpty {
+            let alignedBottom = max(outerPadding, contentBottom - gap)
+            for itemIndex in lastItemIndexForColumn.compactMap({ $0 }) {
+                let currentFrame = items[itemIndex].frame
+                let alignedHeight = alignedBottom - currentFrame.minY
+                if alignedHeight > currentFrame.height {
+                    items[itemIndex].frame.size.height = alignedHeight
+                }
+            }
+        }
         for (index, group) in bottomPinnedGroups {
             let y = max(outerPadding, contentBottom)
             let height = AppGridCollectionMetrics.cardHeight(
@@ -1961,6 +1980,17 @@
         )
     }
 
+    private static func bottomPinnedPriority(for containerID: String) -> Int? {
+        switch containerID {
+        case AppContainerID.uncategorized:
+            return 0
+        case AppContainerID.appleBuiltIn:
+            return 1
+        default:
+            return nil
+        }
+    }
+
     private struct RowItem {
         let index: Int
         let width: CGFloat
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index d2037c6..8152059 100644
--- a/src/Apptag/Info.plist
+++ b/src/Apptag/Info.plist
@@ -19,9 +19,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>8.1.1</string>
+	<string>8.1.2</string>
 	<key>CFBundleVersion</key>
-	<string>20260626.2229</string>
+	<string>20260627.0106</string>
 	<key>LSApplicationCategoryType</key>
 	<string>public.app-category.utilities</string>
 	<key>LSMinimumSystemVersion</key>
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 407ba80..5831d95 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 ## [Unreleased]
 
+## [8.1.2] — 2026-06-27
+
+- 优化样式 2 / 样式 3 的底部特殊容器排版:`未分类`和`Mac 自带`都固定在瀑布流底部并横向占满,`未分类`倒数第二、`Mac 自带`最后显示
+- 样式 2 / 样式 3 中,底部特殊容器上方各列的最后一个普通容器会向下补齐到同一底线,避免短列和底部全宽容器之间出现参差的大空隙
+- 其它 AppGrid 样式、标签数据、排序、主题、Pro 权益和拖拽语义保持不变
+- 版本号更新为 `8.1.2`,Build 更新为 `20260627.0106`
+
 ## [8.1.1] — 2026-06-26
 
 - 优化样式 2 / 样式 3 的 Mac 自带容器布局:瀑布流容器样式下固定到底部并横向占满,避免窄列过高挤压其它分组

--
Gitblit v1.9.3