Repository navigation
Roadmap zh HK
English version: Roadmap
V0.1 出咗一個最小、可 ship 嘅 slice(7 個 hardcoded layout、hotkey、AX permission、DMG)。V0.2 + V0.3 + V0.4 bundle 埋一齊做第一個公開 release(v0.4.0,2026 年 4 月):自訂、smooth animation、drag-to-swap、Workspaces、thumbnail、縱向 seed、多語 UI。V0.4.1、V0.4.2、V0.4.3 係後續 patch。V0.5.0 出咗 Apple notarization,V0.5.1 加咗持續 update detection,V0.5.2 加咗首次啟動歡迎畫面,V0.5.3 調順咗動畫同埋加返 Intel 支援(universal binary),V0.5.4 修咗首次啟動嘅 welcome 同 Accessibility 升級時嘅 stuck 救援,V0.5.5 修咗 quit + relaunch 之後 24 小時冷卻吞咗新版本通知嘅 bug 同埋 Workspaces tab 加返 delete 掣,V0.5.6 加咗 in-app update installer,V0.5.7 加咗用家自訂排列 + 拖邊同步 + Save 掣 dirty-state,V0.6.0 加咗 privacy-first 診斷紀錄 + 持續性 Workspace(pinned apps + 指定 Desktop + enforcement modes),V0.6.1 加咗閒置模式(Free Mode):menu bar 撳一掣,Setpiece 所有自動行為暫停,唔使 quit。V0.7.0 開咗自動化界面(scene:// URL scheme + AppIntents,可以用 Shortcuts / Siri)、加咗 Workspace 每個螢幕獨立 layout,仲修咗拖拽對調同 Free Mode 撳唔返轉頭嘅問題。V0.7.1 修咗上下唔對稱嘅 layout 上下倒轉 apply,同埋 seam-resize 喺 canvas 自訂 layout 度亂咁推窗口嘅問題。V0.7.2 幫 Settings 視窗換上 macOS 26 嘅 Liquid Glass 設計 — Tahoe 上面透明 chrome、浮起玻璃 sidebar、成個視窗半透明。V0.7.3 令再撳同一個 layout 唔會再搞亂已經入位嘅 window,同埋 menu bar 個 menu 撳完唔會即刻閂。V0.7.4 令 layout 嘅幾何唔再受 Dock 喺邊部 mon 影響,多 mon 機再撳同一個 layout 真係完全唔會郁,menu panel 會為生效嗰個 layout 打剔,仲修咗更新檢查會拖住用家一版一版咁爬嘅問題。V0.7.5 修返 macOS 26 上面 Settings 嘅工具列 — V0.7.2 嗰層透明背景搞到 Layouts 同 Workspaces 所有新增/複製/刪除嘅掣通通唔見咗。 V0.7.6 令對話框、sheet 同浮動面板唔會再俾人當成可以排嘅窗,將 V0.7.4 嗰個「每個螢幕都預留 Dock 空間」變成可以熄,令啲窗貼得返底邊,仲修咗一個新版設定檔會令舊版開唔到嘅啟動崩潰。
- 拖拽對調同接縫調整而家可以同時喺每個螢幕運作。 兩個螢幕嘅 Mac 有三個問題,全部喺真 MacBook + 外置 mon 重現過:拖咗去另一個螢幕嘅窗仍然記住喺舊格,下次對調就被扯返去;放手時滑鼠喺另一個螢幕、但個窗中心仲喺 layout 嗰個螢幕,就會同 macOS 自己搬螢幕打交,個窗一半喺一邊、尺寸亂晒;coordinator 得一份全域 snapshot,所以喺一個螢幕 apply 就會令另一個螢幕嘅拖拽對調失效。
-
修正:
PlacementRegistry,即係呢份路線圖之前計劃嘅 per-screen snapshot map。 每個螢幕一份 snapshot。記錄一個螢幕嗰陣會將佢啲窗由其他 snapshot 剔走,同埋清走拔咗嘅螢幕;controller 會搵返擁有被拖嗰個窗嘅 snapshot。AXMoveObserverGroup.setObserved會跨螢幕比較前後嘅 observed set,唔再每次 apply 都拆晒重砌。DragSwapController會將「放落 layout 以外嘅螢幕」當成個窗離開咗 layout(onWindowsLeftLayout),已經離開咗嘅讓位窗永遠唔會被扯返嚟。 -
陷阱:判斷「滑鼠喺唔喺呢個螢幕」要用
NSMouseInRect,唔好用CGRect.contains。 喺螢幕最頂一行,NSEvent.mouseLocation.y == frame.maxY,contains唔計,所以拖經 menu bar 嗰陣會中途放咗個窗。 -
WhatsApp 排得返。 佢個主視窗報
AXSubrole = AXDialog,V0.7.6 嘅 deny-list 會跳過。而家被拒嘅 subrole 只要全螢幕掣 enable,或者最小化同放大都 enable,就會救返(WindowChrome)。AXSheet/AXDrawer永遠唔救。 -
「每個螢幕都預留 Dock 空間」預設熄,settings v5 會幫升級嘅用家熄埋。 V0.7.4 想要嘅穩定性用另一個方法保住:只要個窗喺
TilingFrame.stickyCandidates(而家嘅visibleFrame、預留 Dock、淨係預留 menu bar)任何一個範圍入面對得上自己格,LayoutEngine.plan(alternateFrames:)就當佢已經入咗位。 -
CI 修好: Xcode project 用
../../macbook-resizer搵 SceneCore,repo 改名之後每次 push 都壞;而家改咗做..。 - Tests 438 -> 467。Dock 真係搬螢幕之後再 apply 淨係有 unit test,因為模擬滑鼠事件推唔郁 Dock。喺 macOS 27 驗證過。
-
macOS 27 上面開設定唔會再凍結兼閃退。 喺 macOS 27.0.1 用 v0.8.0,個窗一出現就凍結,跟住個 process 以
NSGenericException死咗:"The window has been marked as needing another Update Constraints in Window pass, but it has already had more Update Constraints in Window passes than there are views in the window." 次次開都係咁,因為設定一開就係 Workspaces 嗰版。 -
原因:一個
NavigationSplitView揼咗喺另一個入面。WorkspacesTab係一個NavigationSplitView,擺咗喺SettingsRoot嗰個嘅 detail column 入面。個窗第一次排版嗰陣,入面嗰個 split 嘅 SwiftUI column 不停報一個新嘅最細尺寸(SplitViewChildController.hostingView(_:didUpdateMinSize:)),每報一次都喺排版途中令 root hosting view 嘅 constraint 失效,於是個 pass 永遠停唔落嚟。喺真 build 入面逐樣排除過:一開係 Layouts(喺同一個 detail column 入面嘅HSplitView)或者 About 就開得正常,熄咗 macOS 26 嘅 title bar 樣式就照樣死。開咗個窗之後先轉去 Workspaces、拉大拉細、閂咗再開,全部都冇死過。 -
修正:
WorkspacesTab改用HSplitView,同LayoutsTab一樣。 Toolbar 搬咗去個 split view 度,照樣橋接到去視窗 toolbar,因為 hosting controller 仍然係個窗嘅 root(V0.7.5 嗰個限制)。喺預設視窗大細,editor 嗰格改前改後都係 304 pt 闊。 -
以後改設定要守嘅規矩:唔好喺
SettingsRoot嘅 detail column 入面再揼一個NavigationSplitView。 用HSplitView。 - Tests:438,冇變。淨係喺 macOS 27.0.1 驗證過。
-
Scene 改名做 Setpiece,而任何識別現有安裝嘅嘢都冇變。 Bundle identifier 仍然係
com.hillman.SceneApp,因為 TCC 係綁住 identifier 同簽名嚟記住 Accessibility 授權 — 換一個就等於一次過靜靜雞收回所有人嘅權限,而且 macOS 會繼續顯示個掣係 ON 但AXIsProcessTrusted返 false,用家嘅體感係「個 app 壞咗」而唔係「我要重新授權」。Application Support 仍然係Scene。用家見到嘅係CFBundleDisplayName。 -
磁碟上個 bundle 保持
Scene.app,而呢個限制塑造咗成次改名。 直到 v0.7.6 為止出街嘅每個 updater 都會檢查[ ! -d "$MOUNT/Scene.app" ],搵唔到就中止,所以 DMG 入面換咗檔名就會令成個用戶群嘅 app 內更新爛晒。UpdateInstaller而家改成攞 DMG 入面嗰個.app(find "$MOUNT" -maxdepth 1 -name "*.app"),令到呢個 updater 流通咗之後,真正改 bundle 名變成一個版本就做得到嘅事。 -
scene://永久保留,setpiece://係增加。 用舊 scheme 砌嘅 Shortcut、Raycast script 同書籤,app 由內部完全睇唔到亦搬唔到,所以舊 scheme 永遠唔會拆。URLRouter.acceptedSchemes收住兩個;五個 test 釘住佢哋,而且攞改名前嘅 router 驗過,喺嗰度有三個會紅。 - 版本號揀 minor 而唔係 patch。 版本字串係 updater 唯一會推到用家眼前嘅嘢;「0.8.0 — now called Setpiece」有人讀,「0.7.7」會被略過。切版本嗰陣 v0.7.6 得六個下載,所以差唔多成個用戶群係由一個叫 Scene 嘅 build 直接跳去叫 Setpiece 嘅,只會見到一次改名。
-
Xcode 27.0 喺
-O下 build Release 唔會再出現 SIL optimizer ICE —— 就係嗰個逼我哋喺 Xcode 26.x 加SWIFT_OPTIMIZATION_LEVEL=-Onone嘅問題,已針對本專案(universal、deployment target 14.0)驗證。但 v0.8.0 特登保留住個 override:一個改名版本唔應該同時改 codegen,否則出問題就分唔清邊樣搞出嚟。優化版 binary 量到 3.2MB 對 120KB,出之前值得搞清楚。 -
Homebrew token
scene→setpiece,用cask_renames.json對映舊 token,令brew upgrade照樣解析得到。實測:brew info --cask chifunghillmanchan/tap/scene解析成chifunghillmanchan/tap/setpiece。 -
安裝說明補咗
brew trust。 同改名無關:Homebrew 7 對未 trust 嘅第三方 tap 係直接報錯唔會問你,所以之前嗰行單行安裝指令會失敗,而且冇任何解決提示。 - Tests 433 → 438。 Universal(arm64 + x86_64),macOS 14+,Apple notarized。
呢個 app 一直到 v0.7.6 都叫做 Scene。呢個 wiki 由而家開始用新名,但已經裝咗嘅
安裝完全冇變:bundle identifier 仍然係 com.hillman.SceneApp,所以 Accessibility
授權唔會斷;資料仍然喺 ~/Library/Application Support/Scene;磁碟上個 bundle 仍然
係 Scene.app;而 scene:// URL 會同新嘅 setpiece:// 一齊繼續用。今次改名只係
改顯示名稱。
-
對話框、sheet、inspector 同浮動面板唔會再被排版。
AXWindowEnumerator以前收晒所有 layer-0 而 AX frame 對得上CGWindowList範圍嘅窗 — 即係一個 app 擁有嘅每一個窗,包括佢啲對話框。開個偏好設定對話框或者「顯示簡介」就會食咗一格、令所有真窗重新洗牌、將最後一個推去toMinimize:喺 Finder 撳個 ⌘I 就足以搞亂一個三分排版。新嘅WindowSubrole(SceneCore/AX)會讀kAXSubroleAttribute,拒絕AXDialog、AXSystemDialog、AXFloatingWindow、AXSystemFloatingWindow、AXSheet同AXDrawer;sheet 同 drawer 係附喺母窗上面,根本冇得獨立擺位。 -
呢個判斷用排除清單,唔用允許清單。 好多 app 根本冇填 subrole,或者填啲自訂嘢。如果一定要
AXStandardWindow先排,就會靜靜雞令成個 app 都排唔到,而唔係淨係多咗一個面板 — 呢種失敗用家完全查唔到原因,只會合理咁認為「Setpiece 壞咗」。所以冇填、空白、認唔出嘅一律照排。AXUnknown同樣特登唔放入清單:佢嘅意思係「個 app 冇講呢個係乜」,係含糊而唔係唔合資格,而且 layer-0 嗰層已經隔走咗通常帶住佢嘅 overlay 窗。呢個唔淨止靠 unit test,仲用真實窗口驗證過:四個到達 layer-0 關卡嘅測試窗入面,AXDialog同AXFloatingWindow嗰兩個而家被隔走,文件窗照排。 -
SettingsStore.dockReserveAllDisplays令 V0.7.4 嘅取捨變成你嘅選擇。 喺每個螢幕都預留 Dock 厚度,可以令 Dock 游走時 tiling rect 保持穩定,代價係冇 Dock 嗰個螢幕底部有一條死位 — 喺真實雙螢幕上實測係 55pt。設定 → 互動 → Dock 空間 可以熄咗個預留,還原做原始visibleFrame:啲窗貼實底邊,同時 Dock 游走嘅位移亦跟住返嚟。提示文字直接寫明呢個代價,冇當佢係免費午餐。預設開,升級嘅用家 migrate 之後仍然係開 — 佢哋本身已經係穩定行為,唔應該喺唔知情下改。單螢幕嘅 Mac 開唔開都一樣,因為一個螢幕嘅最大 inset 就係佢自己。 -
三個用到 tiling rect 嘅地方都讀同一個設定 —
LayoutEngine.plan、drag-swap 最近 slot、seam reflow — 因為 drag 如果對住另一個 rect 度,就會 snap 去錯嘅格。 -
設定 schema v3 → v4,而且之後都向前相容。
AppDelegate.init會將任何 store 初始化嘅 throw 變成fatalError,而decodeWithMigration撞到任何認唔出嘅 schema version 都會 throw — 所以行過新版再返去舊版嘅人,會得到一個開唔到而且冇任何提示嘅 app。呢個喺今次 release 嘅測試過程中實地重現咗,唔係理論:行咗 v0.7.6 個 build 之後settings.json升咗級,跟住 v0.7.5 年代嗰段 code 就喺啟動時死咗,錯誤係Unsupported settings schema version 4。而家高過currentVersion嘅版本會經一個LenientFile(每個欄位都係 optional)寬鬆解碼:攞呢個 build 識得嘅,其餘用預設。真正唔支援嘅版本仍然會 throw,因為嗰啲係檔案損壞而唔係嚟自未來。個檔案喺載入時特登唔會被改寫,所以返去新版嗰陣設定仍然完好;儲存設定就會用呢個 build 嘅版本改寫,但嗰個係用家自己嘅動作。decodeWithMigration同時由 4-tuple 改成 struct — 加多一個 Bool 之後變成三個 Bool 排埋一齊,差一個位就係一個 silent bug。 -
一次過嘅 GitHub star 提示。
StarPromptState(SceneCore)用純值型別收起規則:數真正落地嘅 layout、夠 20 次先問、無論答邊樣都當永久。個計數器喺門檻度封頂,所以長期使用都唔會 overflow;答咗之後就停止郁;解碼寫得保守,人手改過或者未來版本寫嘅內容都唔會令 app crash,亦唔會令已經撳走嘅提示再彈返出嚟。StarPromptTracker(SceneApp)存喺UserDefaults而唔係settings.json,因為佢喺 hotkey 頻率嘅路徑上跳,而SettingsStore.persist()係成個檔案 atomic 重寫 — 同hasShownFirstLaunchWelcomeV1一樣嘅理由。個 row 擺喺 menu panel 入面 Settings 上面,咁就唔會擠走用家開個 menu 本來想撳嘅 workspace 或者 layout。只有行到performApplyLayout尾嘅套用先計數:搵唔到窗嘅會提早 return,唔計。 -
Repository 由
macbook-resizer改名做scene-macos,README 亦都改用動態示範,唔再係一張靜態圖加一條 GitHub 唔會內嵌播放嘅 13 MB MP4 連結。GitHub 嘅永久 redirect 已經針對三條已出街版本依賴嘅路徑驗證過:UpdateChecker會 poll 嘅 releases API(301 → 200)、Homebrew cask 會解析嘅 release asset 下載(200)、同埋網頁 URL(301)。 -
Tests 394 → 433。 十二個
WindowSubroleTests釘死排除清單嘅合約,包括冇填/空白/認唔出嘅保證;十個DockReserveTests釘死兩個螢幕上嘅預留、預設值同單螢幕一致性;四個SettingsForwardCompatibilityTests釘死未來檔案嘅處理;十三個StarPromptStateTests釘死門檻、永久性同解碼容錯。Universal(arm64 + x86_64),macOS 14+,Apple notarized。
-
macOS 26 上面 Settings 嘅工具列返晒嚟。 喺 Tahoe 度,工具列啲掣全部唔見咗:Layouts 嗰版嘅
+、+ Custom、刪除、還原 seed 同 Restore Default Presets;Workspaces 嗰版嘅 New、Duplicate 同 Delete。已經有嘅 layout 同 workspace 仲改得、撳 hotkey 又 fire 得、喺 menu bar 都啟動得到 — 但係成個新增/複製/刪除嘅界面都掂唔到,所以 Tahoe 新用家開 Settings 就搵唔到任何方法加嘢。Issue #4。 -
根本原因:做咗 child 嘅 hosting controller 唔會交出 toolbar。 V0.7.2 為咗做成個視窗嘅透明效果,將一個
NSVisualEffectView嘅 container view controller 設成視窗嘅contentViewController,再將 SwiftUI 個NSHostingController當 child 咁揼入去、貼實四邊。NSHostingController淨係喺佢自己就係視窗嘅 content view controller 嗰陣,先會將 SwiftUI 嘅.toolbar交去view.window.toolbar;做咗 child 就乜都唔交 — 冇 warning、冇 compile error、連 runtime log 都冇。結果個視窗係window.toolbar == nil:唔係得個空 toolbar,而係根本冇 toolbar,所以無論邊個 tab 宣告乜都渲染唔到。 -
點樣 debug 出嚟。(hosting controller 被包住 vs 做視窗根層)×(Tahoe 嘅
fullSizeContentView+ 透明標題列 + 隱藏標題 vs 普通 chrome)四種組合各起一次,layout 完之後查window.toolbar。淨係嗰兩個被包住嘅情況會得nil;兩個做根層嘅都有一條三粒掣嘅 toolbar 睇得見。原本最可疑嘅透明標題列(因為佢同呢個 regression 一齊入)其實係無辜嘅。 -
修法。
NSHostingController重新做返視窗嘅根contentViewController,而.underWindowBackground/.behindWindow嗰層物料改咗喺 SwiftUI 度做background圖層 — 用一個細細嘅 privateNSViewRepresentable,再經WindowBackdrop呢個 view modifier 套用,macOS 26 以下係 no-op。Liquid Glass 個樣完全冇變 — 浮起玻璃 sidebar、透明標題列、成個視窗半透明 — 而原本分兩條分支嘅視窗建構亦都合併成一條所有 macOS 版本共用嘅路。嗰個 call site 加咗註解寫明呢個陷阱,唔好再踩多次。 -
影響範圍。 macOS 14 同 15 一直冇事,佢哋行嘅係冇改過嗰條
else分支。受影響嘅係 v0.7.2、v0.7.3 同 v0.7.4,而且淨係喺 macOS 26。 -
Tests 394/394,冇變。 呢個 bug 喺 SceneApp 入面嘅 AppKit/SwiftUI 視窗底層,而
swift test(設計上淨係跑 framework-neutral 嘅 SceneCore)結構上根本掂唔到 — 所以本來就冇 unit test 捉得到,亦都冇夾硬加個 test 扮捉到。改為直接對住行緊嘅 App 驗證:Layouts 個 toolbar 報返Add, square.on.square.squareshape.controlhandles, Bin, arrow.counterclockwise, Restore Default Presets,Workspaces 報返New, Duplicate, Bin。Universal(arm64 + x86_64),macOS 14+,Apple notarized。
-
Dock 唔再影響 tiling frame。
NSScreen.visibleFrame淨係喺 Dock 當下嗰部 mon 度留返位,而 Dock 會跟住隻滑鼠喺唔同 mon 之間跳 — 即係話個 tiling rect 睇嘅唔係部 mon,而係你隻滑鼠啱啱去過邊。將 Dock 移去另一部 mon 再撳返同一個 layout,每個 slot 都會照 Dock 嘅厚度郁一次,遠遠超出 V0.7.3 嗰 10 pt 嘅 sticky tolerance,所以全部 window 都會重新縮放再按 z-order 重排 — sticky 嘅保證喺最需要佢嘅 setup 上面靜靜雞失效咗。新嘅TilingFrame(SceneCore/Display)將 Dock 嘅預留位統一到所有螢幕:左 / 下 / 右 inset 取全部螢幕嘅最大值,上邊嘅 inset 就各自保留,因為 menu bar 唔會跟滑鼠走。由於 Dock 同一時間只會喺一部 mon 度,個最大值本身就係 Dock 嘅厚度,佢移嚟移去都唔會變,所以個 rect 淨係取決於你部部 mon 點擺。畸形嘅排列會 fall back 返visibleFrame;淨係一部 mon 嘅話,一個螢幕嘅最大值就係佢自己嘅visibleFrame— 同 V0.7.3 完全一模一樣。已接受嘅代價:冇 Dock 嗰部 mon 都要留返嗰條位;開咗 Dock 自動收埋就完全冇代價。 -
拖拽同拉邊都用同一個 frame。
DragSwapController同SeamResizeController而家 construct 嗰陣會俾一個指住TilingFrame嘅visibleFrameOverride(重用返本身為咗測試而存在嘅注入位),所以最近 slot 同 reflow 嘅計算都係對住 layout 真正排入去嗰個 frame,而唔係一個爭成 70 pt 嘅 rect。 -
判斷 window 屬於邊部 mon 改用
screen.frame。AXWindowEnumerator嘅範圍檢查唔再用visibleFrame:中心點落喺 Dock 條位度嘅 window 以前會被完全剔出 plan,而且淨係喺 Dock 啱啱喺嗰部 mon 嗰陣先會發生。「呢個 window 屬於邊部 mon」應該問frame,visibleFrame從來都唔係答案。 -
生效 layout 打剔。
Coordinator.activeLayoutID係@Published,而且淨係喺performApplyLayout成功嗰條路先會 set,所以搵唔到 window、或者中途冇咗 AX 權限嗰啲情況,都唔會為一個螢幕上根本冇反映到嘅 layout 打剔。MenuBarContentView.layoutRow會喺 hotkey 鍵位之後畫個剔,個欄位闊度固定而且每一行都有,所以鍵位永遠對齊。同 Free Mode 一樣淨係記憶體狀態 — 重開之後乜都唔會打剔,因為 Setpiece 根本唔知你啲 window 仲係咪排住。 -
更新檢查自己揀最高版本,唔再信 GitHub 嘅
releases/latest。 嗰個 endpoint 嘅定義係:喺非 prerelease、非 draft 嘅 release 入面,按created_at排最新嗰個 — 而created_at係個 tag 指住嗰個 commit 嘅日期,唔係發佈日期。所以由舊 commit 切出嚟嘅 hotfix 會排喺佢所取代嗰個 release 下面,落後好多版嘅用家就會收到舊嗰個,裝完重開又收到下一個:一版一版咁爬。UpdateChecker而家攞releases?per_page=100,剔走 draft 同 prerelease,再用indexOfHighestVersion揀 — 逐個 component 用數字比較,所以v0.10.0會正確大過v0.9.0,用字串排序就會啱啱相反。冇更新嗰陣個 banner 亦都會清走,唔會喺用家自己手動裝完 DMG 之後仲賴死喺度。 -
測試 371 → 394。 12 個
TilingFrameTestscase 釘死 Dock 嘅合約(單螢幕喺兩種 Dock 狀態下都完全一致、Dock 喺 A 定 B 都出同一個 frame、Dock 喺左右兩邊、上邊 inset 逐個螢幕分開、畸形排列 fall back、以及端到端驗證 quad slot rect 完全唔郁)。11 個HighestVersionSelectionTestscase 釘死版本挑選,包括created_at次序倒轉嗰個 case 同v0.9.0vsv0.10.0陷阱。Universal(arm64 + x86_64),macOS 14+,Apple notarized。
-
Sticky slot 再 apply。
LayoutEngine.plan以前純粹按 z-order 分配 slot,所以任何改動之後再撳同一個 layout 都會全部重新洗牌 — Quads 閂咗一個 window 再開另一個 app,四個 window 全部都會移。而家 plan 會先跑一個 sticky pass:window 現時嘅 frame 同某個 slot rect 差距喺 10 pt 之內(用原有嘅rectsApproxEqual)就claim 咗嗰個 slot,跟住剩低嘅 window 按 z-order 填剩低嘅 slot,多出嘅照舊 minimize。設計上係 stateless — 冇任何持久化嘅 window→slot map 會過期。Sticky placement 嘅目標係 slot 嘅準確 rect,所以細過 tolerance 嘅偏移會自動修正返;拖過位換咗嘅 window 因為坐喺 slot rect 上面,所以再 apply 都保住新位置;撳另一個 layout 因為冇 frame match 得到,所以照舊全部按 z-order 重排。已知限制:唔肯接受 AX 改 size 嘅 app(Apple 系統設定)永遠唔會準確坐喺 slot rect 上面,所以唔會被當成 sticky。 -
Placement.slotIndex。 Sticky 分配可能會claim 咗 slot {0, 2, 3} 而 slot 1 空住,所以 placement 唔再同layout.slotsindex 對齊。Placement而家自己帶住 slot index,Coordinator.rebuildDragSwapObservers改用佢嚟砌lastWindowToSlotIdx(drag-swap 同 seam-resize 都要讀嘅 map),唔再靠 array 位置推。Sticky window 特意留喺placements裡面(目標係佢自己現時嘅 frame)而唔係剔走,咁樣佢哋就唔會失去 drag-swap observer。 -
Window style menu panel。
MenuBarExtra由.menu轉去.window:macOS 原生 menu 撳一下一定會閂,冇得改,所以以前撳 Free Mode 就會閂咗個 menu,要再開一次先撳得到 layout。MenuBarContentView而家係一個 280 pt 嘅 panel,用自訂MenuRowButtonStyle排(圓角 hover highlight、disable 時淡色),內容、次序、thumbnail、hotkey glyph 同所有 localization key 都同舊 menu 一樣 — 冇加新字串。撳 Free Mode / layout / workspace 都唔會閂,checkmark 即時更新;透過捕捉到嘅 hostingNSWindow(PanelWindowAccessor),Settings、onboarding、update alert 同 Escape 可以主動閂佢。 -
Tests 363 → 371。
LayoutEngineStickyPlanTests八個 case 釘實個 contract:全部再 apply 視覺上唔動、閂一個開一個只有新嗰個移、換位保住、overflow minimize 多出嗰個而唔係 sticky 嗰啲、撳另一個 layout 按 z-order 重排、tolerance 邊界 8 pt vs 12 pt、疊住嘅 window 按 z-order 分先後、underflow 空隙報返正確嘅空 slot 數。Universal(arm64 + x86_64),macOS 14+,Apple notarized。
-
Liquid Glass 設定視窗(macOS 26 Tahoe)。
SettingsWindowController加咗 availability-gated 嘅 chrome 設定 —.fullSizeContentView、透明 titlebar、隱藏 title — hosting controller 仲包咗入一個NSVisualEffectViewcontainer(.behindWindow、.underWindowBackground):SwiftUI 嘅containerBackground(for: .window)喺手動 host 嘅NSWindow入面係唔會生效嘅。Sidebar 變咗系統浮動玻璃 panel,成個視窗 blur 住後面嘅嘢。macOS 14/15 保持不透明視窗。 -
五個 tab 統一 chrome。
SettingsRoot加咗共用嘅DetailTabChromemodifier,五個 tab 而家有一樣嘅頂部標題列,Tahoe 上面仲會清走 scroll background(以前得 Workspaces/Layouts 呢兩個有 toolbar 嘅 tab 先有標題列);WorkspacesTab個 list 加咗 8pt 頂部 margin,第一行唔會再貼住浮起 panel 嘅頂。 -
所有 OS 版本一律靠頂。
SettingsRoot嘅 detail frame alignment 由 center 改做.top;About 同 Interaction 唔會再垂直置中。 - Tests 363/363 — 只改 SceneApp,SceneCore 冇郁過。Universal(arm64 + x86_64),macOS 14+,Apple notarized。
-
修正 — layout 上下鏡像。 所有 authoring 界面(canvas editor、縮圖、template 嘅 slot 數學)都係由螢幕頂部量度 unit slot 嘅 y,但係
Slot.absoluteRect(in:)將 unit y 直接放入由底部量度嘅 NSvisibleFrame— 所以任何上下唔對稱嘅 layout apply 出嚟都係上下鏡像(自訂嘅「上 2 格、下 3 格」變咗「上 3 下 2」;Main + Side(縱向)個主窗口跌咗落底;lShapeTop/Bottom 對調)。對稱嘅 Halves/Thirds(縱向)presets 由 V0.4 開始一直遮住咗個 bug。而家 y 軸喺 unit→NS 轉換嗰度啱啱好翻轉一次(同AXWindow.setFrame單次 NS→AX 翻轉一樣嘅原則),LayoutReflow(seam 拖拽用嘅 NS→unit 逆向數學)都跟住翻轉。 -
修正 — seam-resize 對 tree layout。 Canvas 整嘅自訂 layout 帶住過時嘅
template/slotProportions欄位(由複製來源留低);SeamResizeController攞咗呢啲舊資料餵俾LayoutReflow,搞到拖拽 resize 任何一個窗口都可能將其他窗口推去另一個 layout 嘅位置 — 邊個窗口郁視乎邊個窗口 echo 咗 resize event,所以睇落好似隨機。而家 Coordinator 嘅 seam context provider 對 tree 形 layout 一律回nil(自由 resize;reflow 繼續只支援 template)。 -
Tests 357 → 363。 六個新 case 鎖定方向約定:
Slot.absoluteRect上下 anchoring、報告嗰個 5 格情境嘅 regression test(上 2 下 3 要 materialize 返上 2 下 3)、重寫咗嘅LayoutReflowrow 語義(slot 0 = 頂部;佢嘅 seam 係窗口嘅 NSminY邊)。Universal(arm64 + x86_64),macOS 14+,Apple notarized。 - 升級提示。 如果你之前為咗遷就舊行為而倒轉咗自己個 layout,更新之後要重新編輯一次 — 而家點畫就點 apply。
-
自動化界面。 新增
scene://URL scheme(SceneCore 嘅URLRouter+AutomationCommand,經AppDelegate+AutomationDispatcher接駁)同 5 個 AppIntents(Activate Workspace、Apply Layout、List Workspaces、Toggle Free Mode、Set Free Mode),仲有SceneAppShortcuts畀 Siri 用嘅語句。全部指令經Coordinator.handleAutomationCommand,會過 AX 權限同 Free Mode gate。URL scheme 喺 macOS 14+ 用得,AppIntents 要 macOS 14.1+。 -
Workspace 每個螢幕獨立 layout。
Workspace加咗displayLayouts: [DisplayLayoutAssignment](displayName → layoutID)同resolvedLayoutID(forDisplay:)。啟動情境時WorkspaceActivator會 iterateNSScreen.screens,用新嘅Coordinator.applyLayout(id:on:)幫每個螢幕套用對應 layout,冇特別設定就用返主layoutID。用decodeIfPresentdecode,舊workspaces.jsonmigrate 得順。 -
修正 — 拖拽對調用咗舊 snapshot。 window→slot 嘅記錄(
Coordinator.lastWindowToSlotIdx)淨係喺applyLayout時 refresh,所以第二次連續對調會讀到舊位置 — 搵唔到被移走嗰個窗,兩個窗疊喺同一格(連 seam-resize 都一齊受影響,因為共用同一個 map)。DragSwapController而家會 emitonSwapcallback,Coordinator 每次對調之後即刻更新 snapshot。加咗 regression testtestConsecutiveSwapsUseUpdatedSlotMap(RED-checked)。 -
修正 — Free Mode 撳唔返轉頭。
MenuBarContentView之前用@EnvironmentObject睇freeMode,但MenuBarExtra嘅 menu 唔會穩定咁 re-subscribe,所以撳熄 Free Mode 之後 menu 可能仲顯示緊舊狀態。而家 menu 直接用@ObservedObject攞 coordinator(同 menu bar 圖示嗰個 fix 一樣)。 -
測試 317 → 357。 新增 SceneCore 覆蓋
URLRouter/AutomationCommand、DisplayLayoutAssignment,同兩次連續對調嘅 regression。Universal(arm64 + x86_64),macOS 14+,Apple notarized。
-
閒置模式 toggle。 SceneApp 嘅
Coordinator加咗@Published var freeMode: Bool。打開咗嗰陣,Coordinator.applyLayout(_:)同Coordinator.applyWorkspace(id:)喺最頂 early-return — menu click 同 Carbon hotkey 兩條路都靠呢一個 gate 擋走(hotkey handler 本身都係 route 入呢兩個 entry point,所以一個 guard 就 cover 晒 ⌘⌃1-9,0 同 ⌘⌥1-4)。TriggerSupervisor加咗對應嘅var paused: Bool = false,喺handleDirectActivation(workspaceID:kind:source:)入口 check,三個 auto-trigger watcher(monitor / 時間 / calendar)全部 short-circuit,唔會 update cooldown timestamp(避免暫停期間嘅事件「記住」cooldown 影響 toggle 返開之後嘅 fire)。Drag-swap 同 seam-resize 嘅 gate 擺喺Coordinator.rebuildDragSwapObservers入面 AXonMove/onResizeobserver closure 入面(guard let self, !self.freeMode else { return })—— observer 本體唔停,所以 toggle 返開即時恢復,唔使重新 arm。 -
Menu UI。
MenuBarContentView喺 Layouts section 同 Settings divider 之間加咗個「閒置模式」row。打開咗有個前置Image(systemName: "checkmark"),閂咗就 14ptSpacer佔位(同 Workspaces section 嘅 checkmark slot 一樣闊,菜單寬度保持唔變)。Layouts 同 Workspaces 嘅Buttonrow 加咗.disabled(coordinator.freeMode),暫停期間兩個 list 都係灰嘅。SceneAppApp.swift加咗個 privateMenuBarLabelview,@ObservedObject個 coordinator,MenuBarExtra個 icon 喺rectangle.3.group(運作中)同pause.rectangle(暫停)之間切換 ——MenuBarExtra嘅 label closure 唔會自動 subscribe environment object,所以要明確@ObservedObject。 -
State lifecycle。
freeMode只係 in-memory state — 永遠唔寫入settings.json,冇 schema migration。每次 Setpiece 開機都係freeMode = false,唔理上次 session 有冇暫停過。Coordinator.freeMode.didSet將值鏡射落triggerSupervisor?.paused處理 auto-trigger gate;optional chain 處理 launch 早期未configure(triggerSupervisor:)嗰段窄窗口(UX 上唔可能咁早 toggle,但係 defensive correct)。 -
多語。
Localizable.xcstrings新加menu.free_mode:Free Mode(en)、閒置模式(zh-HK 粵語;用 閒置 唔用 自由,因為粵語 自由 偏向「休閒」嘅意思)、自由模式(zh-TW 書面語)。 -
冇改 SceneCore;測試維持 317/317。 純 SceneApp glue 改動。SceneApp 暫時冇
swift test覆蓋 —— 一份 10 步嘅人手 smoke checklist 加咗落docs/TESTING.md做驗收(冷啟動係 OFF、hotkey / 拖邊 / auto-trigger 喺 ON 時靜默、dimmed row + ✓ + 暫停 icon、toggle 返開恢復、relaunch 唔保留 state)。
-
診斷紀錄子系統(Diagnostics subsystem)。 SceneCore 新加
Diagnostics/directory:DiagnosticEvent(10 個 case — layoutFired / instant + animated outcome / 7-step workspace step / triggerFired / suppressed / screenDiff / AX permission / app launch timeout / app quit survivors)、EnvironmentSnapshot配 integer-fieldScreenRecord(resolution / visible frame / scale100 / main flag — deterministic SHA prefix signature,整數 round 過避免 float drift)、EventLog200-entry ring buffer(@unchecked Sendablelock-protected)、DiagnosticWriteractor 包住單一AsyncStream+ 唯一 consumer task(race-safedrain():唔會 per-call siteTask {}走漏,所以 disable 同 queued write 唔會 race)、DiagnosticBudget(newline 安全 truncate、最舊 rotated file 先 evict,2 MB cap,7 日 retention)、GzipWriter(Apple Compression framework + 手寫 gzip header / CRC32 / ISIZE — 用 systemgunzipround-trip 驗證)、SaltStoremode 0600 + disable→enable 重新生 salt(forward secrecy)、DiagnosticHashersalted SHA256 prefix=8 → 11-char base64url、Sanitized*DTO(Layout / Workspace / FocusMode / Trigger / Hotkey)+ExportSanitizerpure transformation。SceneApp 加咗DiagnosticControllercomposition root、EnvironmentCapture(NSScreen→ScreenRecord)、ProcessRunnerprotocol、DiagnosticExporter經/usr/bin/ditto包 zip。 -
Hook 散布喺 orchestration 層。
Coordinator.performApplyLayout紀錄layoutFired(連 snapshot)+layoutOutcomeInstant;WorkspaceActivator.activate每個 stage 都發workspaceStep(quit / launch / settle / applyLayout / focusOn / setActive / banner)連 timing + appCount + survivorCount;TriggerSupervisor紀錄triggerFired同triggerSuppressed(reason:cooldown / alreadyActive / inFlight);MonitorTriggerWatcher每次didChangeScreenParameters都記screenDiff(唔再淨係 connect/disconnect by name);WindowAnimator統計 per-runsetFrameFailures,完成時發layoutOutcomeAnimated— 包括動畫被中斷嗰個 case,舊 run 嘅 summary 會喺 track 被換之前先 emit。所有 inject 用 constructor defaultdiagnostics: DiagnosticSink = .noop,舊 test code 唔使改。 -
Export bundle + GitHub issue 直通。 About tab 嘅「匯出診斷紀錄畀 bug 報告用」會包
events-*.jsonl(active + 過去 gzip 嘅日子)、sanitizedlayouts.json/workspaces.json/settings.json、ring buffer tail(in-memory recent events)、system-info.txt(Setpiece 版本、macOS 版本、AX granted、screen 數、hashID = SHA256(salt)[0:8]— 唔包 salt 本身),同README.txt解釋 privacy contract。NSSavePanelsave 完之後,NSWorkspace.activateFileViewerSelecting跳出 Finder highlight 個 zip +NSWorkspace.open開預先填好嘅 GitHub issue(標題Bug report (Setpiece v<version>)、body 有環境資料、相同hashID,labelsbug,diagnostics)。 -
持續性 Workspace(V0.7 一齊 ship)。
Workspace加咗assignedDesktop: Int?(1-9)、pinnedApps: [String]、同enforcementMode: WorkspaceEnforcementMode(.off/.arrangeOnly/.hideWhenInactive/.quitWhenInactive)。每 field 都用decodeIfPresentdecode,V0.4 舊workspaces.jsonmigrate 順暢。DesktopSwitcher用CGEventpost 一個合成嘅Ctrl+N觸發 Mission Control 「Switch to Desktop N」。WorkspaceAppPolicyEnforcersubscribeNSWorkspace.didLaunchApplicationNotification+didActivateApplicationNotification+workspaceStore.onChange,持續執法 — disallowed = (其他 workspace 嘅 pinned)− (active 嘅 pinned)− (Setpiece 自己嘅 bundle),跟住按 mode.hide()或者.terminate()。Activation flow update 為:quit → switchToDesktop → launch(pinned ∪ appsToLaunch dedup)→ settle(如果冇 launch 就 skip)→ applyLayout → focusOn → setActive → banner。 -
Workspace 編輯器 Save UX。
WorkspaceEditorView帶住個savedBaseline: Workspace。isDirty = draft != savedBaseline控制 Save 掣嘅.disabled(!isDirty);成功update之後 promotesavedBaseline = draft,Save 即時 disable。justSavedflag 撳完 set 2 秒,畫綠色checkmark.circle.fill「已儲存 ✓」label;中途用家再改嘢就即刻清走(避免誤以為 save 咗)。新加workspace.editor.saved+workspace.editor.unsaved三個 locale(en / zh-HK 粵語 / zh-TW 書面語)。 -
Settings v2 → v3 migration。
SettingsStore加diagnosticsEnabled: Bool(default true),decodeWithMigration處理 v2 input 時 default 落新 field,第一次讀完即時 re-persist。SettingsStoreViewModel加setDiagnosticsEnabled+onDiagnosticsToggle: ((Bool) async -> Void)?callback,AppDelegate將佢 wire 落DiagnosticController.enable()/disable()— toggle off 會 drain writer + 刪曬 artifacts + 重新生 salt(下次 enable 時 forward secrecy)。 -
測試 244 → 317。 新
Diagnostics/test directory 9 個檔:DiagnosticEventTests(每個 case Codable round-trip、JSON shape{ts, t, p}、per-event byte cap、privacy contract「冇 plaintext leak」property)、EnvironmentSnapshotTests(signature 穩定性 + 對解像度 / Sidecar reconnect 喺唔同 scale / main display 變 / active display 變嘅敏感度)、HashingTests(同一 salt 出同一 hash + 字符集 + 長度 11)、ExportSanitizerTests(layouts + workspaces + triggers PII drop、within-bundle 對得返、cross-salt 對唔返、Codable round-trip)、EventLogTests(200-cap FIFO eviction + observer fire + 1000 線程並發 append)、DiagnosticBudgetTests(檔名 day parsing、newline 安全 truncate、retention by 內嵌 day、total-budget eviction 保留 active jsonl、gzip compress + systemgunzipround-trip)、DiagnosticWriterTests(race test:enqueue 100 → drain → 全部入到 disk、日子轉換 rotation、deleteAllArtifacts、寫出嚟嘅 line 係合法DiagnosticEntryJSON、active file 喺 cap 度 truncate)、GzipWriterTests(用/usr/bin/gunzipround-trip、RFC 1952 CRC32 vector、重複 input 高壓縮)、SaltStoreTests(file mode 0600、regenerate 出唔同 byte、corrupt file 觸發 regenerate、idempotent delete)。SettingsStoreTests加 v2 → v3 migration round-trip + 持久化測試。共 73 個新 case。
-
用家自訂排列(custom layout build)。 SceneCore 新加個
LayoutNodeindirect enum(.leaf/.hSplit(ratio, top, bottom)/.vSplit(ratio, left, right)),承載 binary split-tree,同 11 個固定LayoutTemplate並存。CustomLayout多咗個 optionalcustomTree: LayoutNode?field,Codable 用decodeIfPresent實現前後兼容 —— v0.5.6 嘅layouts.json照 decode,template-only 嘅 layout 亦唔會寫呢個 key。toLayout()有 tree 就用 tree 嘅flatten() -> [Slot](pure function,遞歸切 unit rect),LayoutEngine.plan唔使改。SceneApp/SceneApp/Settings/CustomLayoutCanvasEditor.swift係新加嘅遞歸 SwiftUI view —— 撳 leaf 出 menu(橫向拆 / 縱向拆 / 刪除呢格),seam 係可拖動嘅 Rectangle,DragGesture.onChanged即時更新 ratio binding。LayoutsTab加咗 "+ Custom" toolbar 掣,插入customTree = .leaf嘅新 layout;LayoutDraftEditor見到 tree 就 route 去 canvas editor。 -
拖邊同步(seam reflow on drag)。
AXMoveObserverGroup而家同時 observekAXResizedNotification同kAXMovedNotification。新SeamResizeController(跟DragSwapControllerinjected-probe pattern)讀 AX 報嘅新 frame、callLayoutReflow.reflow(template:proportions:slotIdx:newWindowFrame:visibleFrame:)—— pure function 支援.twoCol/.twoRow/.threeCol/.threeRow,3-way 中間格會自動判斷邊個邊 moved —— 然後用setFrame將新 rect 寫去非 initiating 嘅 neighbour。Self-fire guard 靠 mouse-button probe 加activeInitiatingIDlatch,Setpiece 自己setFrame嘅 echo 唔會 re-enter。共用DragSwapConfig.enabledtoggle;custom-tree layout 暫未 reflow(留到 v0.6)。 -
Dirty-state Save 掣 + save-persist bug 修補。
LayoutDraftEditor(今個 release 新 introduce,本地@State draftwrapper 包住LayoutEditorView同CustomLayoutCanvasEditor)帶住個lastSavedsnapshot。canSave = draft != lastSaved傳落去兩個 sub-editor,綁喺 Save 掣嘅.disabled(!canSave);成功store.update之後更新lastSaved,Save 即時變暗。Cancel revert 去lastSaved(唔係 original load),save → edit → cancel 會返去上次 save 嗰個狀態。另外 editor 而家用 proper@State draft+ 明確 commit —— 修咗之前.onChange(of: draft.template)喺 sidebar 切換嗰陣誤觸(bindings 轉去第二個 layout,誤以為 template 改咗),悄悄將 template default proportions 寫返 store,洗走用家自訂 proportions 嘅 bug。 -
多 screen AX ↔ NS coordinate 修補。 新加
Sources/SceneCore/Display/DisplayCoordinates.swift集中 AX(全局 top-left)↔ NSScreen(每個 screen 各自 bottom-left)轉換。AXWindow.frame而家返 NS-flipped frame;AXMoveObserverGroup個 observer callback 喺 invokeonMove/onResize之前 flip 一次;AXWindowEnumerator個 center-in-visibleFrame check 都 route 去共用 helper。全部下游(drag-swap、seam resize、plan/apply)喺同一個 coordinate system 入面運作。第二個 screen 放喺主 screen 下面或者左邊嘅 setup,而家 seam target 計得正,window 唔會再飛出 screen 外面。 -
Tests 177 → 244。
LayoutReflowTests(20)cover 4 個支援模板、middle-slot 判斷、clamp 去[0.1, 0.9]、visibleFrame 有 offset(多 screen)、唔支援嘅模板返 nil、退化 input。SeamResizeControllerTests(12)cover 核心 reflow、self-fire guard(mouse / option / disabled / echo)、nil / 未知 window short-circuit、3-way smoke、mouse-up state reset。LayoutNodeTests(17)cover slot-count、跨 split flatten、5 格示例逐字 check、100% 覆蓋 invariant、negative / >1 / NaN ratio 嘅 clamp、Codable round-trip 同 on-disk JSON shape 穩定性。CustomLayoutTests加 5 個新 case(customTree 覆蓋、template fallback、round-trip、冇 customTree 嘅 legacy JSON decode、冇 customTree 時 encode 唔會寫個 key)。DisplayCoordinatesTestscover flip helpers。
-
In-app update installer。 Menu 入面「有新版本」而家會出個確認 alert,三個掣:「安裝並重開」(default)、「睇 Release Notes」(開 GitHub release page,即係 V0.5.6 之前嘅行為)、「遲啲先」。撳「安裝並重開」之後,
UpdateInstaller會 download 新 DMG 去~/Library/Caches/Scene/、用codesign -dv驗簽 + 確認TeamIdentifier=22K6G3HH9G、寫個 detached helper bash script 入/tmp/、用nohuplaunch 佢、然後 quit Setpiece。Helper 會等 Setpiece PID exit、mount DMG、用ditto --noqtn換咗已裝嘅 bundle(用Bundle.main.bundleURL攞返路徑,所以唔係裝喺/Applications都得)、剝走 destination 嘅com.apple.quarantinexattr、refresh LaunchServices、unmount、刪 cache、再open新 Setpiece。 -
Accessibility 授權跨版本保留。 TCC 將授權綁喺 binary 嘅 Designated Requirement 度(
anchor apple generic and identifier "com.hillman.SceneApp" and certificate ... and certificate leaf[subject.OU] = "22K6G3HH9G")。呢個 requirement 喺同一個 Developer ID 簽嘅 release 之間係穩定嘅。ditto保留com.apple.maclxattr — TCC 嘅 per-file pointer,將授權錨定喺呢個 bundle 度。新 install 開機嗰陣AXIsProcessTrusted已經係 true:唔使重新 grant、唔使行tccutil reset、唔使去 System Settings 㩒掣。 -
裝失敗會 rollback。 Helper 喺
ditto之前先 backup 舊 app 去/tmp/Scene.app.bak-$$。如果ditto失敗就 restore backup 同 unmount DMG。Log 寫去/tmp/scene-update-<pid>.log方便事後 debug。 - Bootstrap 注意。 V0.5.5 個 menu 仲係淨係開 GitHub page 嘅舊行為,所以 V0.5.5 → V0.5.6 仲係要手動裝一次。之後 V0.5.6 → V0.5.7+ 就行 in-app installer。
-
SceneCore 冇改;測試仲係 177/177 全通過。 新 file
SceneApp/SceneApp/UpdateInstaller.swift(~150 行),加埋UpdateChecker幾行 surface GitHub releaseassets[]嘅 DMG URL。
-
Quit + relaunch 即時撳 GitHub check 新版本 —
UpdateChecker.startPeriodicChecks()喺 launch 時而家會 bypass 24 小時lastCheckedAt冷卻,靠新嘅forceCheck()helper。之前如果 Setpiece 喺 v0.5.3 已經 cache 咗lastCheckedAt(嗰時 v0.5.4 仲未 publish),而你喺 24 小時內 quit 再 relaunch,就會悄悄 skip 咗 GitHub call,menu bar 完全唔會見到「有新版本」。每個鐘嘅 background timer 同 wake-from-sleep 仍然守 24 小時冷卻 — 嗰啲 fire 唔需要用家 intent,要保護 GitHub rate limit(unauth 60 req/hr per IP)。 -
Workspaces tab toolbar 加返 delete 掣 —
WorkspacesTabtoolbar 而家有trash掣,同LayoutsTab一樣。之前淨係靠List.onDeleteswipe gesture,但係 macOS NavigationSplitView 上幾乎冇人發現呢個手勢,用家以為四個 seeded workspace(Coding、Meeting、Reading、Streaming)係冇得 delete。Inline-swipe 保留做 secondary affordance。 - SceneCore 冇改;測試仲係 177/177 全通過 — 純 SceneApp UI / 生命週期修補。
-
Welcome window 唔再等 Accessibility 先彈 — 一次性 welcome 視窗以前要等
AXIsProcessTrusted為 true 先彈。任何由 ad-hoc-signed v0.4.x 升上 notarized v0.5+ 嘅用家,因為 cdhash 唔對 TCC 失效(toggle 仲 ON 但AXIsProcessTrusted永遠 false),結果 welcome 永遠唔出。新流程:welcome 第一次 launch 即時彈,唔理 AX 狀態,由FirstLaunchWindowController.afterDismiss喺用家 dismiss 之後自動接續 onboarding(如果 AX 仲未授權)。 -
AX 救援指令一開始就睇到 —
OnboardingView唔再將tccutil reset Accessibility com.hillman.SceneApp嗰段(連埋複製掣同貼到 Terminal 嘅步驟)gate 喺撳「再檢查」失敗一次先出。大部分用家喺 System Settings 撳着之後等 2 秒輪詢自動偵測,根本冇撳「再檢查」,所以一直冇睇過救援指令。而家一開就見。 -
返返嚟嘅用家如果未授權,自動彈 onboarding —
AppDelegate.applicationDidFinishLaunching而家會喺 welcome flag 已 set 嘅情況下,callingopenOnboardingIfMissingAX()。咁 TCC 壞咗嘅用家就唔使再喺 menu bar 度搵收埋嘅「Grant Accessibility」。 - SceneCore 冇改;測試仲係 177/177 全通過 — 純 SceneApp UI / 生命週期修補。
-
Native app 動畫升到 60Hz —
WindowAnimator而家按動畫嘅 window 組合揀 AX throttle:任何一個 window 屬於已知 Electron bundle ID(VS Code、Cursor、Chrome、Brave、Slack、Discord、Figma、Notion、Obsidian、Teams)就維持 30Hz;全部 native 就升 60Hz。native app 喺同一段動畫時間內大約兩倍幀數,ProMotion 屏幕上感覺明顯順啲。 -
Duration 按距離 scale — 你設定嘅
durationMs而家當係 ~600pt 對角線移動嘅參考值。短距離縮到 ~70%,長距離拉到 ~140%,兩頭 clamp。AnimationConfig自己嘅 [100, 500]ms 做最後 safety net。 - AX dedup 更 tight — 每個 window 嘅 dedup tolerance 由 0.5pt 降到 0.2pt,拎返少少喺 easeOut 尾段被食咗嘅幀。
-
Universal binary(Intel 支援) —
scripts/build-dmg.sh而家出 arm64 + x86_64 兩個 slice。一個 DMG 裝晒,Apple Silicon 唔使行 Rosetta。macOS Tahoe (26) 係最後一個支援 Intel 嘅版本,呢個係 Intel reach 嘅最後窗口。 -
Deployment target —
MACOSX_DEPLOYMENT_TARGET回到 14.0(Xcode 之前喺 build 機嘅 macOS 26.4 下靜雞雞將其改咗做 26.4)。 -
Xcode 26 workaround —
build-dmg.sh強制SWIFT_OPTIMIZATION_LEVEL=-Onone,因為 Xcode 26 Release optimizer 喺 pre-26 deployment target 會喺 SIL pipeline crash。對 menu bar app 影響可以忽略;Apple 出咗修好嘅 toolchain 之後就可以拎走。 -
177 個 SceneCore unit test 不變 — 三個 smoothness 改動全部喺
WindowAnimator(SceneApp bridge)入面。
-
首次啟動歡迎畫面 — 新 user Accessibility 授權完第一次開 app 會見到歡迎窗口,內有 SwiftUI 畫嘅 menu bar mock + 「打開設定」shortcut。用
UserDefaults.hasShownFirstLaunchWelcomeV1守,彈出嗰刻就 set(唔係 dismiss 時),即使⌘Q中途 quit 都唔會再彈。Settings → About → 「再睇歡迎畫面」可以手動再彈。 -
Dynamic 版本號 — About tab 由
CFBundleShortVersionString直接讀 bundle,唔再靠 String Catalog 常數,將來 bump version 唔使改 catalog。
-
持續 update detection —
UpdateChecker而家每個鐘撳一次 GitHub(跨 device 仲係 rate-limited 一日一個 network call),加埋 wake-from-sleep 會 trigger,長開機嘅 Mac 一個鐘內就見到新版本,唔使等下次 launch。
- Developer ID signing + Apple notarization — Setpiece 而家 Apple notarized。Gatekeeper 接受裝,冇「cannot be verified」提示。Homebrew cask 嘅 quarantine strip 仲照跑但已經多餘。
-
TCC Accessibility 授權跨版本保留 — notarization 穩定咗跨 build 嘅 signing identity,所以 TCC
cdhashbinding 唔再每次新 DMG 都失效。V0.5.x → V0.5.y 升級維持授權。
-
Passive update nudge — 新加嘅
UpdateChecker每 24 小時撳一次 GitHubreleases/latest(per-device debounce 擺UserDefaults),遠端 tag 新過CFBundleShortVersionString就喺 menu bar 頂顯示 tinted 嘅「Update available: v%@ — 撳呢度 download」。撳落去開 release page,user 自己 install DMG。 -
特登唔用 Sparkle。 Setpiece ad-hoc sign,每次 build 個
cdhash都變。Silent auto-install 會每次 update 都令 Accessibility 授權失效,核心功能就冚。留返 DMG 安裝 path,Homebrew tap 個 postflight(清 quarantine)先會照跑。 -
isVersionTag(_:newerThan:)搬咗入 SceneCore 做 public free function(Sources/SceneCore/Settings/VersionCompare.swift)。UpdateChecker只留 I/O 同 state;semver 邏輯唔使 Bundle / URLSession / Xcode 都 test 到。 -
加咗 19 個 unit test 覆蓋 patch / minor / major bump、
vprefix、zero-padding 相等(1.2==1.2.0)、trailing zero、pre-release suffix fail-safe、空字串 + garbage input — 所有 malformed input 返false,唔會誤撳 nudge。 -
Icon 重新壓縮 — 9 個 AppIcon PNG 之前 uncompressed 入返 working tree,重新跑
pngquant拎返 v0.4.1 嘅大小(~672 KB → 156 KB 總和)。 - SceneCore test 數目:158 → 177。
-
ProMotion(120Hz)動畫 lag —
WindowAnimator而家 throttle AX 寫入到 30Hz,加埋每個 window 0.5pt 範圍內 dedup。兩個 Electron app(Cursor、Chrome、VS Code)swap 嗰陣,原本 250ms 嘅動畫唔再拖成秒以上 IPC back-pressure。 -
Workspace「一開就預選咗」 —
activeWorkspaceID改做 session-only state。由WorkspaceStore嘅 disk model 抽走,之前 session 嘅 activation 唔會再喺下次開 app 出現一個用戶冇揀過嘅剔號。舊 disk 嘅activeWorkspaceIDkey 會被JSONDecoder預設 policy ignore,下次寫入時清走。 -
即時 Workspace click —
AppLauncher.quit空bundleIDslist 即刻 return,唔食 5 秒 gentle-quit grace;WorkspaceActivator空appsToLaunch就跳過 1.5 秒 settle。Default seed workspace click 完 ~50ms 就出 layout,唔再係 ~6.5 秒。 -
Layout 再撳一次捉到新開嘅窗口 —
Coordinator.applyLayout記住上次 apply 嘅 layout ID 同時間。5 秒內再撳同一個 layout,enumerate 前加 200ms settle,畀CGWindowListCopyWindowInfo時間登記剛開嘅新窗口。第一次撳 layout 嘅 latency 冇變。 -
158 個 SceneCore unit test —
testSetActivePersistsrename 做testActiveWorkspaceIsSessionOnly,assertion 反過嚟;總數冇變。
-
DMG size 2.8MB → 1.4MB — Swift
-Osizeflag、LZFSE DMG 壓縮、加pngquant壓 app icon。靚仔安裝視窗:自訂背景圖、方向箭頭、icon 位置 pin 死、收埋 toolbar/sidebar。 -
Workspace activation 穩定性 —
WorkspaceActivator套 layout 失敗唔會再誤報「已啟動」;TriggerSupervisorsingle-flight guard 防止 manual + auto 啟動同時 race;日曆 keyword 空唔會再誤撞所有 event。 -
重裝授權指引 — 重新 build 令 TCC cdhash 綁定失效嗰陣,onboarding 視窗直接畀
tccutil reset Accessibility com.hillman.SceneApp指令同一個「複製」按鈕,唔再試唔可靠嘅「toggle OFF/ON」做法。 -
WorkspaceStore.insert防重複 ID guard — 擲.duplicateID(id:),唔再靜悄悄 overwrite。
- Workspaces(頭條)— bundle layout + 要開 app + 要閂 app + Focus mode + auto-trigger。4 個 seed(Coding / Meeting / Reading / Streaming)綁 ⌘⌥1–4。撳一下就 activate 成個 context。
-
5 種 trigger —
.manual(hotkey / menu)、.monitorConnect(displayName)、.monitorDisconnect(displayName)、.timeOfDay(weekdayMask, hour, minute)、.calendarEvent(keywordContains)。First-match-wins 排序、每個 Workspace 30 秒 cooldown(auto-trigger 用;manual 跳過)。 - 7 步 activation — previous Focus Off → quit(gentle + 5 秒 grace + notify-on-fail;唔 force-terminate)→ launch parallel → settle 1.5 秒 → applyLayout → Focus On → setActive + banner。
- Layout thumbnail — menu bar 同設定度每個 layout reference 都即時 render slot 比例 preview。3 個尺寸 reuse:24×16(menu)、32×22(Layouts tab)、40×26(Workspace editor)。
-
3 個縱向 preset seed — Main + Side Vertical(⌘⌃8)、Halves Vertical(⌘⌃9)、Thirds Vertical(⌘⌃0)。透過 V0.2 嘅
applyFutureSeeds機制 migrate 落現有 install(呢個機制嘅第一次實戰)。 - Default hotkey 重新編排 — 10 個 layout seed 全部由 ⌘⇧1-7 改做 ⌘⌃1-9,0,避開 macOS ⌘⇧3/4/5 截圖。用戶自設嘅 hotkey upgrade 時保留。
-
多語 UI —
Localizable.xcstrings入面 133 條 key。Ship 時 English + 繁體中文(港)full parity;部分支援 繁體中文(台)+ 日本語(冇嘅 key fall back en)。 -
跨 store hotkey 衝突 — Workspaces 同 Layouts 共用一個 chord namespace;兩個 store 嘅兩階段
setHotkeyConflictProbesetter 令 block-save 嘅 error message 顯示係邊個 resource 佔住個 chord。 - 41 個新 SceneCore unit test(總 157,V0.3 係 116)— Workspace / WorkspaceTrigger / FocusModeReference Codable、WorkspaceSeeds、WorkspaceStore CRUD + 衝突 + seed migration、WeekdayMask OptionSet。
- 拖任何 placed window 落另一個位就 swap。Source snap,被換走嗰個行 V0.2 動畫引擎(250ms easeOut,跟 Animation tab 設定)。
- Interaction tab — 開 / 閂 drag-to-swap;drag 距離 threshold 可調(10–100pt)。
- 逃生口 — 揸住 ⌥ 拖 free-move(唔 swap);drag 中途撳 Esc cancel,snap 返原位。
- App 層
AXObserverbridge(AXMoveObserverGroup、AXWindowLookup、DragSwapAnimationSink)將 SceneCore 嘅DragSwapController(V0.1 已經寫好)wire 入實際窗口。 - 加咗 24 個 SceneCore test(92 → 116)。
-
11 種 grid template —
single、2-col/3-col、2-row/3-row、2×2/3×2grid、加 4 種 L-shape。每種用 slider 編比例。 -
自訂 layout — save / rename / delete;persist 做 JSON 入
~/Library/Application Support/Scene/layouts.json。V0.1 嘅 7 個 preset 變成可編輯 seed(UUID 穩定)。 - 每個 layout 自訂 hotkey 加 block-save 衝突(一個 chord 只屬一個 layout)。
- Smooth animation — position+size 同步 interpolate。可調:enable toggle、duration 100-500ms、easing(Linear / Ease Out / Spring)。7+ window 自動 fall back instant。
- 設定視窗 — SwiftUI sidebar nav。V0.4 tab 次序:Workspaces / Layouts / Hotkeys / Interaction / About。
-
AX permission fix —
forceRecheck()用AXIsProcessTrustedWithOptions(prompt: false)解 running-process trust cache trap。 - 加咗 66 個 SceneCore test(26 → 92)。
-
Per-display 獨立 layout — v0.7.0 同 v0.8.2 部分做咗:Workspace 可以幫每個螢幕分配 layout(
Workspace.displayLayouts),啟動時套用;由 v0.8.2 開始,拖拽對調同接縫調整可以同時喺每個螢幕運作(PlacementRegistry)。仲未做:standalone(唔經 Workspace)嘅每個螢幕 layout,同埋將一個窗放落另一個螢幕嘅格度,就加入嗰個螢幕嘅 layout(而家淨係喺嗰度放低)。 - Launch at Login — 而家冇 UI,用戶手動加入 Login Items。About 或 Workspaces tab 加個 toggle 就 close loop。
-
Pattern learning — 觀察用戶手動
setFrame嘅 pattern,surface 建議:「我見你下午通常 70/30 Cursor + Chrome,要唔要 save 做 preset?」Local-first;optional LLM pass,用戶 BYOK API key。 -
AI / 自然語言 layout input — 新 hotkey(建議 ⌘⇧Space)開個細 input。打「cursor 左 chrome 右」或者粵語版,LLM parse → layout JSON → 透過
Coordinator.applyLayoutapply。 - Per-app rule — 「Slack 永遠入 slot 4,唔理 user 撳邊個 preset」。
-
Aggressive quit toggle — opt-in 嘅 per-Workspace flag,5 秒 grace 之後
forceTerminate仲活住嘅 app。預設 off,保留 V0.4「永遠唔破壞未儲存 work」嘅政策。 - In-app 語言 switcher — V0.4 只靠系統 locale。畀用戶喺 app 入面揀 language,唔使改 macOS locale,係細但實用嘅加嘢。
- Free-form canvas drag layout editor — EpycZones 嗰種。V0.2 特登揀「grid template + slider」呢個 constrained mechanic,animation path 乾淨啲、Settings UI 細啲。Canvas drag 可以同時 ship,唔需要取代。
- Stage Manager awareness — detect + 尊重 Stage Manager group。
- 多啲 Workspace trigger — location / network SSID / audio device / Bluetooth。每個都加多個 permission prompt + watcher,有需求先加。
- Homebrew Cask submission(要 notarize 先)。
- 跨 Mac iCloud sync Workspaces + Layouts。
- Workspace marketplace / 社群分享。
- Touch Bar / external display widget 快速 activate Workspace。
- Mac App Store 分發。要開 App Sandbox,禁止控制其他 app 窗口。Rectangle、Magnet、EpycZones 等喺 Store 以外嘅原因都一樣。
- iOS / iPadOS port。冇對應 API。
- 跨平台 port。全部 code 靠 macOS 特有 API。
- 完整 automation / Shortcuts 取代。Workspaces trigger 集中 5 種;複雜 automation 去 Shortcuts.app。
English
繁體中文(香港)