ALIGNED
Health Pass
- License — License: GPL-3.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 20 GitHub stars
Code Warn
- process.env — Environment variable access in mcp/server.mjs
- process.env — Environment variable access in mcp/server.test.mjs
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Free, open-source layout editor for multi-page Instagram carousels (Mac/Windows) — one continuous canvas, sliced on export; project files are documented JSON an AI can write/IG 跨頁輪播排版工作檯
ALIGNED for Mac
English: ALIGNED is a free, open-source layout editor for multi-page Instagram carousels — the whole carousel is one continuous canvas, images cross the page seams, and it slices on export. Built by a working ad film director in Taiwan.
Official site (English) → aligned-app.github.io/en — downloads, feature tour and FAQ. Mac (Apple Silicon, macOS 13+, notarized) / Windows 10+ 64-bit. GPL-3.0; the name and logo are trademarks — if you fork, please rename. The documentation below is in Traditional Chinese.
IG 輪播的跨頁對齊排版工作檯(Mac・免費・開源)。
由台灣廣告導演 Armin Kao/小高 依自己的工作流打造。
跨頁對齊是招牌:整個專案是一條連續畫布,圖可以橫跨頁縫,IG 滑動時畫面連續。
拖曳吸附、參考線、群組對齊、九顆濾鏡、影片頁(匯出 mp4)、輕量範本交換、
系統字型+字型檔匯入——桌面大螢幕十頁攤開,一眼驗收。

▶︎ 一分鐘功能導覽(有新功能會隨版本更新)
下載
這兩個連結永遠指向最新版,發新版不用改。想看各版本更新內容或抓舊版 → Releases。
📱 iPhone/iPad 版 — App Store(免費下載,進階功能 Pro 訂閱、7 天免費試用)
Mac 系統需求:Apple Silicon(M1 或更新)、macOS 13 Ventura 或更新。
已通過 Apple 公證(Developer ID 簽名+Notarization),下載後直接打開即可,不需要任何繞過步驟。
Windows 系統需求:Windows 10(64 位元)或更新,需要 WebView2(Win11 內建,Win10 多數已隨 Edge 裝好)。
安裝檔尚未做程式碼簽章,第一次開會跳 SmartScreen 藍色警告 —— 點「其他資訊」→「仍要執行」即可。
Windows 版目前少兩件事(Mac 版有、Windows 版還沒有):
.alignproj單檔開不了。那是 Apple Archive 容器,靠 macOS 內建的aa打包。
Windows 請開專案資料夾裡的project.json(素材放同層的assets/)——同一份專案,只是沒裝進單檔。- 影片頁與動畫的 MP4 匯出不能用。合成器是 Swift+CoreImage 寫的,只長在 Apple 平台。
動畫的畫面預覽正常,靜態頁匯出 PNG 也正常。
給 AI 用(這個 repo 的另一半重點)
ALIGNED 的專案檔是文件化的 JSON——任何 AI 讀完規格就能直接產生「打開即可編輯」的專案:
docs/ALIGNED-AI-SPEC.md。
Claude/GPT 排草稿 → ALIGNED 開檔微調 → 匯出發佈,這條流程已實測(手寫 JSON 經 App 本人的解碼器驗證)。
Local MCP(Beta)
repo 內附本機 MCP server,讓支援 MCP 的 AI 不必手寫 JSON,就能讀取、驗證、建立與修改
ALIGNED 專案。它透過 stdio 在使用者電腦執行,不連模型 API、不上傳專案,也不需要雲端伺服器。
npm install
npm run mcp
目前提供十項工具:除了檔案檢查、驗證、建立、修改與單頁預覽,也能讀取正在執行的 ALIGNED App、
取得目前選取、直接修改畫布並共用 Undo。檔案修改預設另存副本,明確要求覆寫時會先留 .bak;
即時修改則以 project ID+revision 防止蓋掉使用者同時操作。安裝設定與能力邊界見 mcp/README.md。
自己編譯
npm install
npm run tauri dev # 開發(真 App 殼)
npm run tauri build # 產 .app(需 Xcode Command Line Tools)
授權
- 程式碼:GPL-3.0(見 LICENSE)。
- 「ALIGNED」名稱與 Logo 為商標,不在授權範圍內——fork 請改名,避免混淆。
- 內嵌字型皆為 OFL 1.1,清單與版權見 public/fonts/LICENSES.md。
- 提交 PR 即表示你同意:你的貢獻以 GPL-3.0 釋出,並額外授權作者將其納入 iOS 版(雙授權)。
個人專案,回覆與維護盡力而為。This is a personal project; replies are best-effort.
以下為開發技術文件(引擎移植的完整歷史與地雷記錄)——對貢獻者與想懂內部構造的人有用,
一般使用者到上面為止就夠了。
ALIGN Core
ALIGNED 引擎的 TypeScript + Canvas 移植。一核多殼的那個核:
Mac(Tauri,先出)→ Android(Capacitor)→ Windows。與 iOS 版並行,不取代。
計畫全文:../未開工企劃/Android跨平台企劃.md(私有工作區文件,不在本 repo)
跑起來
npm install
npm run dev # http://localhost:5173
npm run build # tsc --noEmit + vite build
./difftest/run.sh # 對齊引擎 vs Swift 原版的差分測試
open http://localhost:5173/selftest.html # 瀏覽器內互動自測
右上角可以開任何 project.json、切換範例、按「匯出 PNG」。
拖曳元件=移動並吸附、拖曳空白=平移、兩指捲動=平移、捏合=縮放。
測試策略(引擎移植必守)
「看起來對」不算移植成功。兩層驗證,兩層都必須先證明自己抓得到 bug
(把修正拿掉跑一次,確認會失敗),否則全綠只代表沒在測。
difftest/run.sh— 對齊引擎與 iOS 的 Swift 原版跑同一批亂數測資(400 筆),
逐案比對 frame/吸附旗標/參考線/等距徽章。已用三種突變驗證有效
(閾值 8→7 抓到 8 筆、weak 誤比遠端邊抓到 14 筆、頁中心線寫成左緣抓到 4 筆)。filtertest/run.sh+/filtertest.html— 九顆濾鏡與 iOSFilterEngine的
逐像素比對。參考器連結的是 App 真正的原始檔(腳本自動剝掉 UIKit 那 30 行,
剝不掉就報錯),配方不是手抄的。目前九顆的平均誤差全在 2.7/255(1%)以內。selftest.html— 用真的指標事件驅動 Editor,驗證
「指標座標 → 專案座標 → 吸附 → 寫回 frame」整條鏈。⚠️ 踩過的洞:一開始的六個案例量的全是「位移差」,注入 7px 絕對偏移竟然全過——
固定偏移在相減時抵銷了,等於絕對座標精度根本沒測到。補了「邊界精度」案例
(貼著元件左緣兩側各點一次)才抓得到。
仍未覆蓋:整頁 CSS zoom 與捲動下的座標偏移(那需要 WebKit/Chrome 兩種語意各跑一次)。textlibtest/run.sh— 文字庫(2026-09-14):全 Unicode 字元判斷、iOSTextMemo.swift與src/core/textmemo.ts
同一批測資逐值比對,以及 iOSTextMemoSync編成命令列、與桌面版src/textlib.ts讀寫同一個資料夾的互通情境。
面板另有textlibui.html自測(headless Chrome 用 CDP 照真實時間跑;--virtual-time-budget會卡住)。
拿真實專案檔來測:範本是 AppleArchive/LZFSE,不是 zip,用系統的 aa 解:
aa extract -i "../TemplateForge/範本_輪播全集/17|工作人員名單 Credits(7頁).alignproj" -d /tmp/x
結構
src/core/ ← 平台無關,Android 殼原樣共用。**不准出現任何 Tauri/DOM/桌面專屬的東西**
schema.ts 專案 JSON 解碼(iOS Swift Codable 的形狀翻譯)
geometry.ts 座標系、頁矩形、旋轉 AABB、cropRect 語意
fonts.ts 字型家族/字重 → 具體字面 → 檔案(AppFontCatalog 的移植)
render.ts canvas 渲染。`renderPageCanvas` 是**匯出與編輯預覽共用的唯一入口**
align.ts 對齊引擎(AlignmentEngine 的逐條移植,有差分測試)
filters.ts 九顆濾鏡(查找表+顆粒貼片+網屏表,有差分測試)
paper.ts 整頁紙張(三層:multiply 紙色 → screen 抬黑 → softLight 纖維)
export.ts 原尺寸 PNG 匯出
src/editor.ts ← 互動層:指標事件、命中判定、拖曳、view 變換。**DOM 只出現在這裡**
src/main.ts ← Mac 殼的組裝
public/fonts ← 23 個內嵌字型,從 iOS 專案 Sources/Fonts/ 複製
已驗證(2026-08-01,Phase 1 第 1 塊)
拿 92 份真實範本、2,423 個 block 做過欄位清冊,不是照 Swift 原始碼猜的。
- schema 解碼:7 頁範本完整讀進來並畫出,形狀、字色、字體、直排方向全對
- Swift Codable 的三個形狀(都已處理):
CGRect→[[x,y],[w,h]]、
enum 關聯值 →{"text":{"_0":…}}、AttributedString→ 文字與屬性交錯的陣列 - 字型內嵌:源樣明體、思源宋體等 23 個字面在 canvas 上渲染品質良好
- 橫排文字:字距、行高、對齊、中英混排、墨跡貼合(
actualBoundingBoxAscent
= iOSTextInkInset的等價物)都落在對的位置 - 直排字元進距:
size + kern,與 TemplateForge 的產生公式吻合
(範本 frame 高度比排版高度大 10%,是 TemplateForge 刻意留的估值餘裕,不是誤差)
已驗證(2026-08-01 第二輪,Phase 1 第 3 塊)
- 對齊引擎移植完成且與 Swift 原版逐案相同(400 筆亂數測資,0 不一致)。
含三段磁性、頁邊/頁中心、使用者參考線、跨頁絕對與相對對齊、等距徽章。 - 互動畫布可用:單一連續 stage(頁與頁無間隙,就是專案的座標語意)、
選取(用真正的旋轉矩形判定而非外接框)、拖曳、即時吸附與參考線、平移縮放。 - 旋轉的坑一開始就避開了:被拖的元件自己也換算旋轉外接框——
iOS 端 2026-08-01 才修好的同一個 bug,這裡從第一版就是對的,並有自測案例守著。
已驗證(2026-08-03 第三輪,Phase 1 第 4–5 塊)
- 九顆濾鏡逐像素平均誤差全在 2.7/255(1%)以內(見上一節的四個坑)。
- 整頁紙張已實作。三種紙的纖維參數與濾鏡 c1/c3/c4 完全相同,直接共用貼片。
目前 92 份範本與 iPad 上的專案都沒在用,但那是 iOS 已上線的功能,先做起來放。 - 匯出管線:原尺寸 PNG(scale 1,畫布尺寸即像素),檔名補零帶頁碼。
photoLibraryOrder()保留了 iOS 那個「存相簿要反轉頁序」的產品細節
(相簿是最近建立的排前面,倒著寫才會排成編輯順序)——存檔案不需要反轉。 - 預覽與匯出走同一支
renderPageCanvas,所見即所得不會分家。
有紙張時走離屏逐頁渲染,沒紙張時直接畫(拖曳才不會每格 getImageData)。
已驗證(2026-08-03 第四輪:編輯殼)
- 屬性檢視器+頁面膠捲(獨立元件,照「三塊可分離」約束):文字全套樣式、
形狀、媒體濾鏡/遮罩/外框即時調;膠捲點頁跳頁。方向鍵微移(⇧=10)。 - 存檔 ⌘S:
encodeProject把模型編回 iOS 形狀(frame→[[x,y],[w,h]]、
enum→{_0}、文字→AttributedString runs 且顏色烤進 runs、updatedAt 剪掉毫秒
——iOS 的 ISO8601 解不了毫秒)。json 直接原子覆寫;.alignproj寫回解包資料夾後
用aa archive -a lzfse重新打包,首次覆寫自動留.bak。
⚠️aa archive打包的 .alignproj 尚未在 iPad 實測過能否匯入——出貨前必驗。 - 復原 ⌘Z/重做 ⇧⌘Z:「已提交影子」快照制,連續滑桿/按住方向鍵在 900ms 窗口
內合併成一步。標題列 ● =有未存變更。 - 往返測試抓到一個真解碼 bug:SwiftUI AttributedString 的顏色存線性 RGB
(colorHex 8A8A8A=sRGB 0.541 的 run 存 0.255)。原本直讀當 sRGB,中間調文字
全體偏暗。解碼(線性→sRGB)與編碼(sRGB→線性)兩個方向都已修正。
⏸ 待小高定奪(會影響架構,先別自己決定)
.alignproj的跨平台容器 ✅ 2026-08-21 小高拍板=① zip(讀舊 LZFSE 相容、寫出改 zip、三平台共用;未動工)。原題:現在是 AppleArchive + LZFSE,只有 Apple 平台解得開
(Mac 可以呼叫系統的aa,Android/Windows 沒有)。三條路:
① 跨平台版一律改用 zip(要處理與 iOS 既有檔的相容);
② Mac 讀寫.alignproj、Android 只吃裸project.json;
③ 自己實作 LZFSE 解碼(成本最高)。- Mac 版的系統預設字型。iOS 端
fontName為空=PingFang TC。Mac 也有 PingFang,
所以 Mac 可以與 iOS 像素級一致;但 Android 沒有,必然落到內嵌的 Noto。
→ 要「Mac↔iOS 完全一致」還是「Mac↔Android 完全一致」?兩者不可兼得。 - Mac v1 的範圍:檢視+匯出就出貨(快、能立刻建立存在感),
還是要做到能編輯才出?後者至少多兩到三個月。
濾鏡:能烤成表的就不要重算
移植濾鏡踩到的四個坑,每個都讓我一度以為「差不多對了」:
- 色調曲線不能照控制點自己刻。 App 的 CIContext 工作空間是 sRGB,
CIToneCurve在該空間下的實際輸出不落在控制點上——faded 的 0/64/128/192/255
出來是 3/71/129/174/211,照控制點刻會得到 26/79/136/192/235,整組顏色偏掉。 - 顆粒層的 alpha 也是隨機的。
CIColorMatrix只動 RGB 向量、alpha 直接穿過去,
而CISoftLightBlendMode是 alpha 感知的。當成不透明層混,誤差 12;照標準
alpha 合成,誤差 2。 CIRandomGenerator的重複單元只有 256×256(兩軸位移 256 誤差皆為 0),
所以整層顆粒烤成貼片平鋪就精確。別想著導原始噪點自己算——它連 alpha 都是隨機的,
存 PNG 走預乘會失真,讀回來根本不是原值。CIDotScreen不是 sin·sin。 拿它擬合殘差 37/255=完全不對。實測它是
「週期內相對位置 × 亮度」的函數,角度 −0.3 rad(不是配方寫的 +0.3,
CI 的 y 軸朝上所以符號相反)、間距 5.0 px。烤成 32×32×17 的表(17 KB)
之後 c1 的誤差從 47.8 掉到 1.65。
沒有完全模型化的只剩 CIBloom(演算法未公開),影響 b5,誤差 2.69。
已驗證(2026-08-04 第五輪:長文框/排開/空欄位)
- 長文框(isBodyFrame):固定容器、超出裁切、垂直對齊(無洞時)——
drawBodyFrame。 - 排開文字(文繞圖):
textwrap.ts逐條移植 iOSTextWrap.swift——只作用於長文框
+橫排+未旋轉(iOS 拍板同款);side(預設,圖的中心定側、不在句中跳圖)/around
(島嶼,橢圓用真實弧寬)/push(整列讓開);margin 18;線條的洞貼墨跡;
「一個字都塞不下的段留空、絕不滲進洞」。 - 空欄位框(範本欄位):墨色依頁底亮暗切換(iOS emptySlotPlaceholder 同款),
匯出也畫——這是範本的本體不是編輯器裝飾。雙擊空欄位=選檔填圖(=iOS 的
點欄位挑照片),檢視器也有「選擇/更換圖片影片」鈕;選影片自動抓海報並轉型,
裁切與拉直重置、遮罩描邊濾鏡排開設定保留。
直排的「逐字體欄距補償」不必移植:把 iOS 那三段公式代開會發現最終欄距恆為
1.22em × 行高倍數,字體被消掉了——那套補償的用途是抵銷 CoreText 自作主張套上的
字體自然欄距,canvas 是自己逐欄擺位的,沒有東西要抵銷。
已驗證(2026-08-04 第六輪:影片預覽會播+八點手把)
- 影片在畫布上會播(
videopool.ts):靜音、自動循環,<video>直接當
CanvasImageSource。三個刻意的取捨寫在檔頭——匯出/縮圖仍畫海報圖(決定性優先,
同一份專案匯出兩次要一樣)、有濾鏡的逐格套在長邊 512px 的暫存畫布、整台 stage
重畫節流到 20fps。一個檔一個播放器,同檔不同濾鏡共用解碼;換濾鏡與新匯入的影片
由每一拍自己接上,不用重啟播放。 - 八點手把(角=縮放、邊=裁切,iOS 2026-07-05 的定案逐條照搬):
- 角(圓點):對角錨定,照片/影片等比鎖、不動 cropRect;形狀自由拉;
最小尺寸 canvasWidth×5%(iOS 同值);未旋轉時兩條移動中的邊都吃吸附
(等比時兩軸比距離、近的贏,同 iOSsnapAspectLockedEdges)。
旋轉過的元件照拉:指標換算回未旋轉座標系算尺寸,再由錨點反推 frame。 - 邊(長條):真裁切——frame 與 cropRect 同步收放,所以錨定側的畫面
逐像素不動;往外拉露出藏起來的部分,上限是 cropRect 撞到素材邊緣。
起手先把 aspect-fill 實體化進 cropRect((0,0,1,1) 是哨兵值不是字面值,
不攤開的話下筆瞬間會從置中裁切跳成整張拉伸)。只給未旋轉的媒體。 - 螢幕上太小就不長手把(角 28px、邊 56px)——抓取範圍是固定螢幕尺寸,
小元件會被手把整個吃掉、變成拖不動。 - 文字兩種都不給——文字的框是貼字盒,尺寸由字級與內容決定。
- 角(圓點):對角錨定,照片/影片等比鎖、不動 cropRect;形狀自由拉;
- 驗證:自測 36/36,四個新案例都做過突變測試——拿掉旋轉補償→「旋轉後對角視覺
位置不動」FAIL;drawMedia 不看即時影格→「影片畫即時影格」FAIL;裁切時 cropRect
不同步收→「錨定側逐像素不動」FAIL(像素差 218);拿掉素材邊緣上限→「往外拉到底就停」
FAIL(寬度衝到 1400、cropRect 跑到 −1.75)。
裁切的驗收刻意用逐像素比對而不是數字:frame 與 cropRect 只要有一邊算錯,
照片就會滑動或縮放,而數字(寬度、crop 值)各自看起來都還「合理」。
另外跑真 App 端對端(videoprobe.html+public/samples/_probe,開發專用不進包):
畫布上讀到 timecode 00:00:02.867、頁面縮圖仍是海報=兩條路確實分開。
headless Chrome 的
--virtual-time-budget不能拿來驗影片播放:虛擬時鐘下解碼
不前進,會得到「明明會播卻測出 STATIC」的假陰性。要驗真的播放就開 App 用screencapture隔兩秒抓兩張比對。
已驗證(2026-08-04 第七輪:補上與 iPad 的渲染缺口+開關)
先做「開 iPad 檔會畫錯」的那類——少畫一個效果不會報錯,只會安靜地不一樣:
- 文字陰影(soft/strong +陰影色):iOS
TextShadowStyle的 em 參數表逐值照抄。
兩個 canvas 的坑:shadowOffset/shadowBlur不吃 CTM(所以自己乘進變換矩陣的
線性部分,旋轉過的文字陰影才會跟著轉);canvas 的shadowBlur是兩倍標準差、
CALayer/SwiftUI 的 radius 就是標準差,所以要 ×2。 - 文字底色:原本畫成「填滿 frame 的直角矩形」是錯的——iOS 是往外撐 0.25em、
圓角 0.2em 的圓角矩形(.padding(-pad)),貼字盒本身不變。 - 圖片拉直(
rotationDegrees):iOSMediaBlockRendering的旋轉分支移植——
放大後的整張圖繞自己中心轉,再平移讓裁切區中心落到視窗中心,平移量本身也要
轉過(iOS 修過的 parity bug)。沒轉過的圖走原路徑,逐位不變。 - 使用者參考線:以前吸得到但看不到。垂直的是頁內座標=每頁重複,水平的跨整台
stage;顏色同 iOSguideColor。只有編輯畫布畫,匯出與縮圖乾淨。
接著補「引擎會畫但沒有開關」的(做了引擎卻碰不到=等於沒做):
檢視器新增:排開文字(開關+單側/兩側/上下,形狀與媒體共用)、長文框
(開關+框大小+框內對齊)、段落間距、直排欄序、陰影/陰影色、文字底色。沒選元件時顯示專案級設定:整頁紙張(全專案單一)+逐頁背景色。
順手修一個 WYSIWYG 破口:畫布的
renderStage沒收到濾鏡貼片,紙張在編輯時
不會顯示、匯出卻會。已把資產餵進 Editor。驗證:自測 46/46。四個渲染缺口全部做過突變測試(拿掉陰影→取樣 0、底色回舊幾何→
框外全白、不吃拉直→上緣帶不傾斜、參考線塗透明→取樣不藍)。檢視器的開關則用真的
DOM 事件驅動(跟使用者按的是同一顆 checkbox),驗值有沒有寫進 block。
已驗證(2026-08-04 第八輪:Mac 版能自己從無到有做完一份專案)
補的是「iPad 有、Mac 沒有」裡最擋路的那批。純邏輯都抽成 core/ 模組,跟殼分開測。
- 頁面操作(
core/pages.ts):新增(繼承前一頁背景)/刪除/相鄰換位/複製一頁。
頁不是容器、是共享座標的一段,所以每個操作都只是平移 x +背景重新編號;
貫穿規則同 iOS:block 屬於它中心所在的那一頁。膠捲上滑過縮圖才出現操作鈕。 - 多選與群組(
core/group.ts):⇧/⌘ 點加減選、空白處拖曳=框選、整組拖曳、
對齊六向、等距分布(≥3 個)、整組複製/刪除、⌘A 選當前頁。
對齊基準是選取集合自己的外框(不是頁面),鎖定的略過——與 iOS 同。整組拖曳的吸附只算被拖的那一個,位移原封不動套到其他成員:拿整組外框去吸,
成員之間會被拉扯變形。
平移改成「按住空白鍵或中鍵」——空白處拖曳的語意讓給框選(Keynote 同款),
觸控板兩指捲動本來就能平移。 - 畫布尺寸(
core/canvas.ts):新專案(⌘N,比例/翻轉/頁數+即時比例預覽)、
既有專案改比例。短邊固定 1080、長邊照比例(翻轉的 9:16 是 1920×1080)。與 iOS 的一處刻意分歧:iOS 改比例時完全不動 blocks,多頁專案會因為頁寬改變
讓第 2 頁以後的內容掉到別頁去。這裡改成保住「哪一頁」+頁內座標,尺寸一樣不縮放。 - 鎖定開關、圖片拉直(rotationDegrees 的 UI)、裁切比例(1:1/4:5/3:4/16:9/9:16,
短邊不動、以中心為錨)。 - 輕量範本匯出(
stripToTemplate+ Rustpack_template):一份沒有 assets 的.alignproj,圖/影片全變空欄位框,外框/遮罩/描邊/濾鏡/排開/紙張全留,
裁切與拉直重置。AirDrop 給 iPad 就能開來填自己的照片。 - 自測 71/71。過程中被自己的測試抓到一個真 bug:
load()換專案時沒清多選集合,
上一份專案的 id 會殘留下來。
已驗證(2026-08-04 第九輪:影片頁匯出 mp4)
Mac 版最後一塊。做法是把影片交還給 AVFoundation——videotool/ 編出一支alignvideo 命令列工具打包進 App,它的濾鏡、紙張、合成器全部直接編譯 App 的真原始檔
(FilterEngine.swift 與 VideoPageExporter.swift 裡 UIKit-free 的那段,只做機械式剝離),
所以配方與 iPad 是同一份,不是重寫的近似品。
- 管線與 iOS 相同:① 烤(每支影片轉正、裁切、縮到 block 的像素大小,之後擺位只是平移)
② 合成(各佔一軌、短的循環補到最長那支、自訂 compositor 逐格依 z 序疊)。
長度=該頁最長的影片;聲音只取最長那支不混音;mute可關。 - iOS 註解裡那三個用血換來的細節一起搬過來了:AVURLAsset 要留著(track 是 weak 反向
參照,回收後 insert 會 -12780)、用影片軌自己的 timeRange 而不是 asset.duration、
音軌要夾限到 masterDuration(否則 -11841 整份失敗)。 - 分工:靜態圖層、遮罩、外框由 Mac 的畫布先渲染成 PNG(那條路已逐像素比對過,
重畫一次反而是新的漂移來源);影片的解碼/裁切/循環/音軌/編碼交給 AVFoundation。 - 匯出面板:影片頁自動標記,可選 24/30/60 fps 與有聲/靜音;「全部下載」會把靜態頁
存 PNG、影片頁合成 mp4,逐頁回報進度。 - 驗證:工具端拿真影片跑過(1080×1350、30fps、遮罩+濾鏡+上下圖層+紙張,抽格檢查
全部落在該在的位置);打包進 .app 的那一支也單獨跑過;JS 端的層堆疊有自測
(層序、頁內座標、偶數尺寸、遮罩/外框、裁切傳遞)。自測 74/74。 - 發版腳本已補:巢狀可執行檔要先自己簽(hardened runtime 下沒簽的內嵌 binary
會讓公證直接退件),順序由內而外 alignvideo → .app。
已驗證(2026-08-04 第十輪:桌面該有的手感)
小高逐條點名的七件,加上一批 Mac 的效率語彙。
- 匯出台重做成畫廊:燈關掉、介面退場,只留作品。三種排法與 iPad 對齊:
分開(一次一張、方向鍵翻、頁碼點)/連續(頁貼著頁,檢查跨頁接縫)/
摺頁(卡片留縫、露出下一張邊緣)。影片頁在預覽裡會播。
說明文字用小字+寬字距(排版標準:拉字距只給小字)。第一版做成暗場,小高退回(見下一輪):桌面不缺螢幕,缺的是看得夠近。
- 有聲/靜音改成 icon 開關(單色線性 SVG,不用文字也不用 emoji)。
- 參考線:專案面板可顯示/隱藏、加垂直/水平線、逐條微調與刪除;
畫布上直接拖得動,拖出頁面外=丟掉(Photoshop 的語意)。 - 頁面縮圖可拖曳換序(指標事件自己做——HTML5 drag 在 WKWebView 裡拖曳影像與
落點都不受控;而且我們要的提示是「插在哪兩張之間」)。 - 雙擊裁切過的照片=搬照片:框一動不動,動的是框裡的畫面(這才是裁切的用意)。
位移換算dx × crop.w / frame.w,夾在 0…1 內不會拖出空白。 - Mac 的效率語彙:滑鼠靠手把按住 R 拉角=旋轉(⇧ 卡 15°)、⌥ 拖曳=複製、
⇧ 拖曳=鎖軸、右鍵選單、⌘D 複製/⌘L 鎖定/⌘0 全覽/⌘± 縮放、
游標即說明書(手把上顯示縮放/裁切/旋轉,參考線上顯示可拖)。 - 自測 79/79,五個新互動都做過突變測試。其中一個測試自己被抓包:R 旋轉那案
原本從 45° 轉到 90°,差角剛好是 15 的倍數——卡不卡格都會過,等於沒測;
改成原始差角 23.2° 才真的咬得住。 - 順手補:自測頁的
run()沒有 catch,案例丟例外時整頁停在「執行中…」什麼都不說。
現在會把錯誤與堆疊印出來,並在標題顯示最後跑完的案例(卡住時一眼看出卡在哪)。
🧰 踩到的環境坑:連續密集改檔會把 Vite 的 dev server 卡成半更新狀態,
症狀是自測頁停在「執行中…」——程式沒錯,重啟 dev server 就好。查了三輪才發現。
已驗證(2026-08-05 第十一輪:匯出台是一台可縮放的窗)
小高退回暗場版,理由很準:「這麼大的螢幕下,光編輯器裡的狀態就已經很好看了;
把它固定成一個形式反而不好看。」 桌面不缺螢幕,缺的是看得夠近。
- 兩種牆,可切換並記住:白盒(純白,美術館 white cube,預設)與
暗房(#121216,關燈看片)。中間試過米白,小高退回——
「米白在 Mac 上太像文書編輯器,純白比較有藝術感」。這條成立:
排版標準「底不用純白」本來就標了媒介邊界(純展示品例外),匯出台是展示面不是閱讀面。白牆+白頁沒有明度差,所以邊界完全交給陰影(分層只用一種手段,不加框);
暗房版把同一道陰影加深。整組顏色走 CSS 變數,切換只換一個data-theme。 - 三種排法共用同一台可拖曳、可縮放的視窗(
src/gallery.ts)。
縮放才是主角,排法只是把同一批頁面重新掛一次:- 分開=整張看到;連續/摺頁=對齊高度、左右自己捲(就是「像編輯頁一樣滑」)
- 起手式上限鎖 100%——小專案不該被放大到糊掉
- 頁面在內容層裡以原始匯出像素排版,縮放只做一次 transform,
所以 100% 就真的是 1 像素對 1 像素,不是「大概那麼大」
- 匯出預覽的影片是真的在播——上一輪寫「影片頁在預覽裡會播」是錯的:
renderOpts()從來沒有把即時影格餵給匯出台,那條 12fps 的重畫計時器
一直在重畫同一張海報圖。現在分成兩條:預覽走previewOpts()(多餵videos),
存檔仍然重畫成海報圖版(stillCanvas())——同一份專案匯出兩次必須一模一樣,
抓「當下那一格」會讓 PNG 隨手速改變。抓法:real Chrome + CDP 直接讀
#shots canvas的像素指紋,每 0.9 秒取一次。
headless 驗不了這件事(不解碼,四次指紋完全相同),這是第二次踩到同一個坑。 - 手感:拖曳平移、兩指捲平移、⌘捲動/雙指撐開縮放(以游標為錨)、
雙擊在全覽與 100% 之間切換(Preview 的語彙)、⌘0 全覽/⌘± 縮放/⌘1 原尺寸。
分開模式方向鍵翻頁時縮放刻意不動——放大看某個細節再逐頁比對同一塊,
是桌面才有的看法。 - 點作品=翻到「分開」看大張;拖畫面拖到一半放開不算點(4px 門檻)。
單頁下載改成頭上的「下載這頁」,作品本身不再是一顆按鈕。 - 摺頁模式每張卡下掛一張作品牌(頁碼);作品牌與影片徽章是說明不是作品,
反向縮放讓它們的字級永遠一樣大(scale(1/k))。 - 匯出台開著時鍵盤整組讓給它——底下編輯器的 ⌘0/⌘± 意思不同,會打架。
- 自測 86/86。新的七案都在瀏覽器裡跑,四個關鍵行為做過突變測試:
拿掉錨定補償 → 錨點案 FAIL;拿掉拖曳門檻 → 點作品案 FAIL;連續模式也套寬度限制
→ 連續案 FAIL;渲染忽略videos→ 海報/即時影格案 FAIL。視窗會拆成獨立元件(不是留在 main.ts)就是為了這個:指標 → 內容座標的換算
只有在瀏覽器裡才錯得出來,跟 Editor 同一個理由。
已驗證(2026-08-05 第十二輪:面板釘在側欄、圖層清單)
小高:參考線該是工具列的 icon,點了面板才出現在側欄;側欄的逐頁列表用不到,
換成圖層清單。
- 工具列兩顆 icon(參考線/圖層)=側欄上方的釘板。面板釘在上面(自己捲、
上限 46vh),屬性照樣在下面——不是切換模式,不用互相讓位。 - 參考線面板:顯示/隱藏、鎖定、新增垂直/水平、逐條改數值與刪除。
「隱藏」只是不畫(線還在、吸附照舊);「鎖定」是畫布上滑鼠碰不到
(hitGuide直接回 null,所以游標與拖曳兩條路一起關掉)。
鎖定存在專案裡(guidesLocked)——encode/decode 都是 spread 直通,
iOS 的 Codable 會忽略不認得的鍵,對相容性沒有風險。 - 在畫布上按到參考線=側欄自動切到參考線面板,並把那一條的標籤加粗標出來。
- 圖層清單:只列「現在在看的那一頁」(跨頁全列會變成一長條沒人看得懂)。
由上而下=由前而後;類型圖示+名字(文字用內容前 14 字,那才是他認得出來的東西);
hover 出現鎖頭;點列=選取(⇧/⌘ 加減選);拖曳換前後。
跟著看的那一頁走:有選取跟選取、否則跟畫面中央,平移換頁時自己換過去
(onPageInView只在真的變了才發,不是每格)。 - 側欄的逐頁背景色列表改成只有現在這一頁——功能留著,列表拿掉。
- 自測 91/91,四個新案例都做過突變測試:鎖定不擋命中 → 鎖定案 FAIL;
清單改成由後而前 → 三個圖層案全 FAIL;zIndex 指派方向反了 → 落地案 FAIL;
拿掉按到參考線的通知 → 通知案 FAIL。applyLayerOrder拉去core/group.ts才測得到——留在 main.ts 的 hook 裡量不到。
另外自測抓到一個真的疏漏:拖曳起手的setPointerCapture沒包 try/catch,
合成指標事件會丟例外把整條 onMove 打斷(真機不會,但這是測試該擋下來的東西)。
已驗證(2026-08-05 第十三輪:右鍵吃掉版面、圖層縮圖)
- 右鍵選單長出子選單,桌面該有的跨頁操作進來了:
複製到第…頁/移到第…頁(頁內位置不動,只換所屬的那一格)、
空白處右鍵給「第 N 頁 ▸ 複製這一頁(含內容)/在後面插入空白頁/往前往後/刪除」。 - 膠捲的懸停操作列拿掉了——換順序用拖的(比按箭頭快、看得到落點),
其餘操作進右鍵。少一排長期佔位的按鈕。 - 拖曳時卡片會浮起來跟著游標(原位留一張淡的、浮起那張帶旋轉與陰影),
長按 240ms 也算拿起來。只把原卡變淡、看不到被抓的那張,手感上像卡住。 - 圖層列有縮圖了(比照 iPad 的 26px 縮圖,再多補一項):
媒體畫真圖且依 cropRect 取那一塊(看到的就是畫布上的那一塊)、
形狀畫自己的顏色、文字留字形圖示。 - 排開的死角補上提示:排開只咬得住長文框,這一頁沒有長文框就直接說
「選那段文字,把長文框打開」。功能一直都在(引擎第五輪就移植完、有逐像素測試),
但開了沒反應會被當成沒做——這是說明問題不是功能問題。 - 自測 96/96。
retargetToPage/applyLayerOrder都拉進core/才測得到。⚠️ 又抓到一個「過關過得莫名其妙」的測試:跨頁複製原本從第 1 頁出發,
(to - from) * w和to * w在 from=0 時完全一樣——把來源改成第 2 頁,
突變才咬得住。五個新案例現在全部驗證過會失敗。
已驗證(2026-08-05 第十四輪:參考線的命中順序)
小高回報:線壓在圖上時,拖到的是圖不是線。
- 命中順序改成「手把 → 參考線 → 元件」。原本元件先命中,結果只要線壓在圖上
就永遠抓不到那條線——線只有 5px 寬、元件是一大片,先問元件等於把線埋起來。而且游標本來就已經是這個順序(滑過去顯示可拖曳的箭頭),
所以之前是「游標說得到、手做不到」——行為跟說明書不一致,比單純難用更糟。 - 不想被線搶走就鎖定(上一輪做的參考線鎖定正好是這條的退路):鎖住之後
同一個位置拖到的就是元件。 - 回歸測試兩案:線壓在圖上拖 → 線動 620、圖不動;鎖住後同樣位置拖 → 線不動、圖動。
退回舊順序跑一次,第一案如實失敗(線 500(應 620) 圖 x=320(應 200))
——就是小高看到的那個狀況。自測 98/98。
已驗證(2026-08-05 第十五輪:參考線自己也會吸附)
小高:先排版、用參考線對位,之後每一頁都能照著複刻——所以線本身也要吸附。
snapGuide(core/align.ts):拖參考線時咬元件的左/中/右(水平線咬上/中/下)、
頁的邊與中線、其他參考線。候選集刻意鏡射resolvePosition/snapResizingEdge,
線咬到的位置與元件咬到的一模一樣——不然「用線對好位」之後元件還是對不上。- 跨頁複刻:垂直參考線存的是頁內座標(每頁重複繪製),所以候選也全部換算成
頁內座標;吸附強度=強時別頁的元件邊也算——上一頁那張圖的左緣在頁內 300,
在第 2 頁拖線就會咬到 300。這正是他要的「複刻前一張」。 - 咬到什麼就把那條畫出來(沿用既有的參考線繪製),不然「為什麼停在這裡」沒有答案。
- 吸附強度「關」就真的不咬(與元件拖曳同一個開關)。
- 自測 101/101,三案都做過突變測試:不吸附 → 兩案 FAIL;只認同頁元件 →
跨頁複刻案 FAIL;忽略「關」→ 關閉案 FAIL。
已驗證(2026-08-05 第十六輪:真專案卡頓+濾鏡保真——診斷與驗證都走多代理工作流)
小高拿真專案(分鏡展示.alignproj:7 頁、29 block、15 支 4K .mov 共 130MB)回報
「在電腦上非常卡」+「濾鏡預覽跟輸出差異還是很大」。
卡頓:病根與修法
實測抓到成本結構:「CPU 光柵化 4K 影片 drawImage」單價 16–26ms/支/格,
而舊架構是 15 條 4K 解碼全開 × 每秒 20 次整台七頁全重畫(文字排版無辜:
七頁全排版才 0.4–0.8ms)。Chromium 的 GPU 路徑扛得住(120fps),但 canvas 一掉到
CPU 光柵(=WKWebView 的常態)就崩到 9.8–12.8fps——與回報症狀同形。修法五刀:
- 只解碼看得見的(videopool):視野外的影片 pause,畫面停在最後一格;
可見性用旋轉 AABB,與渲染裁切同一個形狀(審查抓到:用未旋轉 frame 的話,
轉 90° 的影片會「畫得到卻停止解碼」=凍結,已真機重現後修正+自測)。 - 有新影格才重畫:比對 currentTime。舊版 readyState≥2 就每拍強制全台重畫。
- 影片全部先轉進縮小暫存畫布(4K→長邊 1080)——重畫畫的是便宜的 canvas,
不再讓<video>直接進 renderStage 吃每次 25ms 的 YUV 慢路徑。
每拍 8ms 轉檔預算+輪替:多支同時入鏡時各自降 fps,UI 永不被卡死。 - 視野裁切(render.ts
viewRect):畫面外的頁/block/參考線整批跳過。 - 桌面底色+陰影快取(editor):shadowBlur 只在 view 變時重烤(key 含 dpr)。
matchMedia 每格重建也一併收掉。
匯出台同場修:videoPageSet 開台算一次、零影片不開計時器、只重畫露在
畫廊視窗內的影片頁、無紙張時就地 renderPage(不再每秒 12 張全解析度離屏)。
匯出台開著時擋掉拖放(shots 是快照,拖進來會靜默過期)。
驗證數字(同機同法,CPU 退化協定):fitAll 9.8→120.5fps(12.3×)、
9 影片頁 12.8→120.5(9.4×)、單影片頁 37.3→120.5(3.2×);GPU 對照組無退步。
行為驗證:解碼數 15→10→3 隨視野收斂、畫面照樣在動、匯出台非當前頁指紋凍結
=真的沒重畫。自測 102/102。
濾鏡「預覽=輸出」三個根因(全修)
- 空間性濾鏡的解析度依賴:顆粒/網屏/bloom 是像素單位,預覽在 512px 暫存
畫布套完再放大=顆粒大一圈。applyFilter增加 scale 參數對齊輸出頻率
(不封頂——大影片塞小 block 時 scale>1 同樣成立)。 - CI 工作空間:合成器的
CIContext()預設是線性空間,但九顆濾鏡全在 sRGB
下校準(FilterEngine.ctx 明寫)。→ 已修在 iOS 原始檔 VideoPageExporter.swift
(iPad 同 bug,小高拍板同步修),build.sh 的暫時補丁移除、生成層回歸純抄寫。 - 輸出色彩標記:DeviceRGB→sRGB、pixel buffer 補 709 三件套標記。
A/B 數字(乾淨 709 標記素材、alignvideo 輸出 vs canvas+JS 濾鏡):
無濾鏡基準 MAE 2.43/255、a1 濾鏡歸因誤差 ~0.7/255、c1 通道均值差 ≤2/255——
「幾乎一樣」達標。紙張雙重套用(靜態層 JS 一次+合成器一次)也修了
(still 烤圖 filters: undefined)。
已知殘留(記錄,暫不動):
- 未標色彩的來源影片:預覽(Chrome 猜 601)vs 輸出(AVFoundation 猜 709)
飽和色會偏——非這次管線的鍋,iPhone 實拍片都有標記。要更穩可在 bake 對
無標記 source 明示指定。 - 輸出比來源短一格(119/120);sRGB 編碼標 709 傳輸函數是業界折衷,
嚴格色彩管理的播放器暗部會有極輕微差。 - 同檔同濾鏡放兩個不同尺寸 block 時,濾鏡 scale 取第一個 block 的幾何。
🧰 過程備忘:
--virtual-time-budget這次連 selftest 都會卡死(媒體管線互動),
驗證改走 CDP 輪詢document.title;效能量測要防「getImageData 把 canvas
踢下 GPU」的觀察者效應——指紋抽 50×50 小塊,別整張讀。
大素材測試專案:把 .alignproj 解到暫存夾後,在public/samples/下建 symlink 指過去(命名 perf,build 會剝掉;用完刪掉——指向暫存路徑的 symlink 隔天就斷)。
已驗證(2026-08-05 第十七輪:真 App 影片凍結——WebKit 的 detached <video> 陷阱)
小高在真 App 開《分鏡展示》:「影片不動」。診斷路徑值得記,因為開發環境完全重現不了:
- 先排除平台:
assetprobe.html(新開發探針,?path=指絕對路徑)丟進 tauri dev
的真 WKWebView——asset:// 播 4K 完全正常(rs=4、play=ok、currentTime 前進)。 - 再上診斷儀表(
?open=<路徑>=跟 ⌘O 同一條開專案路;?diag=1=影片池狀態
每秒寫進狀態列,螢幕截圖就讀得到):播放中 15、本拍轉 0 格——
15 支都「在播」(paused=false),currentTime 卻全部凍住。 - 對照探針唯一的差別:探針的
<video>掛在文件裡。
→ WebKit 對 detached<video>不推進播放(paused 還是 false,純凍結,很陰);
Chromium 沒這規矩,所以自測、驗證工作流全綠、真 App 全凍。
修法一行本質:影片池的播放器掛進畫面外的隱藏容器(#videopool-host,left:-99999px——不能用 display:none,那個也會讓 WebKit 停解碼)。
修完真 App 實measure:本拍轉 4 格/25ms(≈6ms/格 4K→1080)、畫布 16 萬像素在動。
fitAll 15 支全入鏡時每支約 5fps(8ms 預算輪替、UI 保持流暢),放大進單頁=20fps。
🧰 環境坑又+1:Vite dev server 連續吃幾十次改檔後又卡成半更新(全白頁),
重啟就好——這是第二次,看到「頁面全白/自測卡住」先重啟 Vite 再查程式。
診斷旗標整理:?sample=(dev 樣本)、?open=(真檔案)、?diag=1(影片池儀表)、?export=/?panel=/?theme=(截圖驗證)。探針頁 build 時全部剝掉。
已驗證(2026-08-05 第十八輪:真兇——WKWebView 把 rAF 節流到 1Hz)
第十七輪修完影片凍結後小高說「還是很卡」,追出三層互相遮掩的問題,這輪是最後一層也最深的:
- 「影片不動」=WebKit 凍結 detached
<video>(第十七輪修)。 - 「還是很卡」第一回=開到 /Applications 裡 8/4 的舊版——App 沒有版本標示,
誰都分不出來。已加 build 時間戳(vite.config.ts__BUILD_STAMP__,
狀態列直接顯示「build 08/05 22:26」這樣)。 - 「還是很卡」第二回(新版也卡)=Tauri 的 WKWebView 把 requestAnimationFrame
節流到 1Hz——視窗在最前景、document.visibilityState === "visible"也一樣
(儀表實錄:重畫 1/s rAF 1/s vis=visible)。整個 App 的畫面每秒只刷新一次,
拖什麼都一格一格——這才是「每次開都卡」的真兇,而且從第一天就存在,
跟專案大小無關。同一個視窗裡 setInterval 完全正常(影片池 20Hz 都在跑)。
修法:rAF 看門狗(editor.startWatchdog)——rAF 心跳正常(Chromium)時看門狗
永遠不出手;心跳超過 90ms 沒跳(WKWebView 的 1Hz 節流)就由 16ms 計時器代打 paint。
修完真機:重畫 19/s×0.8ms(影片節拍主導;互動時上看 60fps)、rAF 依舊 1/s(沒差了)。
為什麼三輪才抓到:所有效能驗證都在 Chromium(rAF 健康),真 App 只驗過「單張
截圖的靜態正確性」,從來沒驗過「更新率」。跨引擎的殼,節拍器本身也要驗。
診斷儀表(?diag=1)現在會直接吐 rAF/s——這數字不對,其他都白談。
已驗證(2026-08-06 第十九輪:第四層——冷解碼器堵死主執行緒+WebContent 被系統砍)
第十八輪裝了看門狗小高還是「卡得要死」。這輪先立了兩條新的量測紀律,然後一次挖穿剩下的病灶:
量測紀律:
- 螢幕真相連拍——頁面內部的計數器(paints/s)說了不算,
screencapture -l <winID>
連拍 12 張數「螢幕上」真正變化幾次才算。修前:12 張全同(螢幕完全沒動);
修後:12/12 張全不同。 - 黑盒子 beacon(main.ts,
?diag=1且 port 5173 才啟用)——頁面每秒把
主執行緒堵塞度(100ms 節拍的實際誤差)、pump 耗時、重畫數 POST 到本機 5199,
頁面死掉的瞬間有沒有 pagehide 也會記(有=JS 自己重載;無聲斷訊=行程被外力砍)。
不碰螢幕、不搶焦點,死前最後一秒都看得到。
黑盒子照出來的兩層新病灶:
- 冷解碼器堵死主執行緒:開專案後 15 條 4K 解碼暖機中,
drawImage(video)會
同步等 GPU 整整一秒(實錄tickMs≈1004,單次長任務最高 6.4 秒)——
開專案後主執行緒被埋 10–20 秒=「一開就卡死」的體感本體。
修法:改用 requestVideoFrameCallback——rVFC 舉手=影格已合成好,這時
drawImage 是現成 IOSurface 的便宜拷貝(1ms 等級);沒舉手絕不碰 video 元素。 - 15 條 4K 解碼把 WebContent 逼死:載入後約 45 秒,WebContent 被系統無聲砍掉
(無 crash report、無 jetsam、無 pagehide),wry 靜默重載=使用者眼裡「專案自己
消失」。dev 模式下?open每次重載又重新解包,一晚堆出 84 個 124MB 暫存目錄。
修法:MAX_LIVE=6——同時真播放上限 6 支,按畫面上 block 面積取大者,
其餘凍在最後一格(全覽時每支才百來 px,無感);加document.hidden全停
(背景不燒解碼)。 - 順手:tauri.conf.json 視窗加
backgroundThrottling: "disabled"(Tauri 2.3+
正式 API,對應 WKWebView inactiveSchedulingPolicy;wry #1246 / tauri #5250)。
多代理研究的結論:精準 1Hz 對應 WebKit 的 VisuallyIdle/DOMTimer 對齊機制,
macOS 上「視窗全可見但 WindowServer 認為 App 停止更新視窗」就會中;
更深的私有 API 解法(_setWindowOcclusionDetectionEnabled:NO等,含 cargo check
過的 objc2 骨架)備而未用,在 scratchpad 研究紀錄裡,目前配置+看門狗已夠。
修後同專案實測:主執行緒最大堵塞 6423ms→2ms、pump 單拍 1983ms→1ms、
WebContent 45 秒必死→全程活著、螢幕連拍 0 變化→12/12 全動。selftest 102/102。
同晚跟進(小高實測回報):
- 「影片抽格、不像 24 格」——rVFC 只舉手、拷貝批進 20Hz pump = 24 格片天生收不滿。
改成 rVFC 舉手當下就拷(copyPlain,跟片子原生節奏走;單次拷貝 0.3ms 等級),
pump 只剩濾鏡版(逐像素、貴、維持預算制上限 20fps)與生命週期。 - 「綠色亂碼破圖」——第一版歸因(清暫存誤刪開著的專案)錯了:重開專案重新解包
後同一支照綠。真兇=Tauri asset 協定串流影片時特定 range 回錯資料。定罪過程:
16 支全是標準 8-bit avc1 4K(排除異類編碼)→ 裸 WKWebView 用 file:// 讀同一支,
底部 25% 綠色佔比三輪取樣全 0.0000(排除 WebKit 解碼器)→ AVFoundation 匯出乾淨
(排除檔案本身)→ 只剩 asset://(它每個 range 硬切 1MB、走 wry 自訂協定層轉交)。
修法=影片改走殼層內建的 127.0.0.1 媒體伺服器(src-tauri/src/mediaserv.rs,
教科書式 Range、隨機 port+token、只服務登記過的素材目錄、ACAO:* 配 video 元素crossOrigin="anonymous"保 canvas 不 taint)。驗證:curl 五種 range 形態逐位元組
與 dd 比對全同、整檔比對全同、416/403 正確;WKWebView 經伺服器串流同一支,
綠色佔比三輪 0.0000。圖片與海報照舊 asset://(整檔載入沒有 range 問題)。
⚠️ 清aligned-mac-*暫存前仍要先確認沒有活著的 App 實例在用(另一顆真雷)。
教訓:「卡」不是一個問題,是四層問題疊著互相遮掩(舊版本→影片凍結→rAF 節流
→主執行緒被堵+行程被砍)。每修一層都「真的修了」但體感依舊,因為下一層還在。
量測要選「使用者感受的那一層」:螢幕連拍與主執行緒堵塞度,比任何內部計數器誠實。
已驗證(2026-08-07 第二十輪:群組等比縮放手把)
多選時群組外框右下角長一顆手把,拉了整組等比放大縮小(左上角釘死)。
逐條照 iOS updateGroupScale,兩處刻意不同:
- 外框用「旋轉後」外接框的聯集——iOS 的群組拖曳 2026-08-01 已經修成這樣,
但它的群組縮放還停在原始 frame 聯集;照舊寫法,群組裡有轉過的元件時
手把會長在看得見的範圍裡面。這裡直接用修好的那版(自測 (j) 守著)。 - 等比解用「投影到起手對角線」——iOS 只吃橫向位移(那是手指),
桌面是滑鼠,往哪個方向拖都得跟手。與單選等比角同一手法。
字要跟著框長大是這個功能最容易漏的那半:fontSize、manualWidth/Height、
點制的 kerning/lineSpacing 全部乘同一個倍率(em 制的字距與行高不用動,
它們吃的是已經縮過的 fontSize)。鎖住的成員不收進來=原地不動。
吸附照單選等比的規矩:右緣與下緣比距離、近的贏。
自測 +4 → 106/106,四個新案例都做過突變測試:拿掉 fontSize 縮放、外框改回未旋轉
聯集、鎖住的也跟著縮、位置不乘倍率——四種改壞法各自被抓到(105/106 FAIL)。
🧰 過程備忘:第一版兩條案例是測試自己寫錯——(h) 沒算到
load()會跑autoFitText,文字框高度不是我寫死的那個(改成從實際外框推手把位置);
(j) 「故意抓錯位置」那一下落在空白處=變成框選,把選取清掉了,
第二次真抓時已經沒有多選(補一次 selectMany)。驗證腳本本身也會有 bug,
全紅時先懷疑測試。
已驗證(2026-08-07 第二十一輪:影片修剪+1.0.1/1.0.2 出版)
修剪是選配、不設上限(與 iPad 的「匯入就強制剪、上限 30 秒」刻意不同,小高拍板):
右鍵影片 →「修剪影片…」開視窗(底片條+進出點把手+播頭+整段平移),
選好按完成就切一份新檔換上去,原檔留著。匯入超過 30 秒時狀態列軟提醒一句。
相容性有查證過,不是猜的:maxDuration: 30 全 iOS 專案只出現在 VideoTrimView
(匯入 UI)一處;Storage/ 零筆時長檢查、VideoBlockPreview 沒有時長假設、VideoPageExporter 是算出最長片長當主軌沒有上限。所以 Mac 存的長片專案回 iPad
會正常開/播/匯出,代價只有「專案檔大、iPad 匯出慢」——不是 bug,是重。
裁切本身交給 alignvideo trim <來源> <輸出> <起秒> <迄秒>,配方與 iOS 完全相同
(AVAssetExportPresetHighestQuality + .mov + timeRange)。不用 Passthrough——
那個只能切在關鍵影格上,使用者拉到哪就該剪到哪。
驗證分三層:
- 工具層:真影片剪 1.0–2.5 秒,時長 1.5 秒✓,且原檔 t=1.0 的影格指紋 ==
剪出 t=0.0 的指紋、原檔 t=0.0 不同(證明真的切在對的位置,不只是長度對)。 - 範圍計算:
nextRange()抽成純函式,自測 +7 條(夾最短長度、不出界、
整段平移長度不變),四種改壞法各注入一次全被抓到。 - 真 App 端對端:
?trimtest=1臨時鉤子跑完整管線(不碰螢幕、結果寫檔外部驗收),
真專案 4K 片 3.958 秒 → 剪出 2.000 秒、3840×2160 保留、海報重生、block 素材已換、
狀態列「已修剪:2.0 秒」。驗完鉤子已拆。
🐛 這一輪抓到一顆正式版也會中的 hang:
capturePoster用的是沒掛進 DOM 的<video>——WKWebView 對 detached 的播放器不推進解碼,seeked永遠不來,
整條匯入/修剪管線就靜靜卡死(沒有錯誤、沒有逾時)。同一顆雷影片池修過一次
(見 videopool 的hiddenHost),這裡是第二個現場。已改成掛進隱藏容器+15 秒逾時。
另外:海報一律走 asset://(一次性讀檔,與匯入同一條驗證過的路),
媒體伺服器只給播放用。
出版:1.0.1(卡頓四層修正+綠色亂碼+群組縮放)與 1.0.2(修剪)都已簽名公證,release/ 內,Gatekeeper 模擬下載驗收 Notarized Developer ID。
已驗證(2026-08-07 第二十二輪:介面回饋三修+1.0.3 出版)
小高實際用了一天後的介面回饋,三件都做完並以 headless Chrome 截圖+CDP 點擊驗證:
- 磁鐵 icon 改成真磁鐵(
index.html吸附選單旁的 glyph):原本是細線 U 形+兩短橫,
讀起來像字母 U;改成單一封閉路徑的粗腳馬蹄形(腳厚 3.6、底帶同厚)+兩極刻線,18px 下可辨。 - 工具鈕終於有開關狀態:
syncPanelButtons()一直有在切.onclass,
但樣式表從來沒定義.island button.on——狀態切了等於沒切。補一條
藍底藍字(#2F7CF6 15%),參考線/圖層/資訊鈕共用。 - 上排開發資訊預設收起:頁數/尺寸/block 數/build 章與選取座標是開發儀表,
收進新的「資訊」鈕(i 圈 icon,localStorage align.showinfo記狀態);
狀態訊息(已儲存/匯入失敗/修剪中)不受開關影響,照常直接寫#meta。
CDP 驗證三態:預設空 → 點開出摘要+鈕亮 → 再點收回。自測 113/113。
出版:1.0.3 已簽名公證(SHA e1d17395…),/Applications 已更新。
已驗證(2026-08-07 第二十三輪:開場首頁+字型系統+1.0.4 出版)
小高點的兩塊功能,同一輪做完:
- 開場首頁(App 內限定):最近專案(
localStorage align.recent,路徑+第一頁 148 高
JPEG 縮圖,開檔與存檔時記錄、上限 12 筆)+新專案/開啟+範例連結。Esc=先逛範本
(首頁底下墊的就是範本樣本)。上排的樣本切換器在 App 內收掉(開發用,首頁已取代)。
首頁開著時快捷鍵只放行 ⌘N/⌘O,其餘不往編輯器漏。驗證旗標?home=1讓瀏覽器也能看版面。 - 字型系統(剪映語彙):字型選單分三組——介面字體(內嵌八套)/自訂(匯入的字型檔)/
系統字體(這台電腦裝的字型,殼層fontdb枚舉、一家族回一個代表面、
有中文名用中文名)。選單最底「+ 匯入字型檔…」=動作項,匯入存 App 資料夾UserFonts/(重開還在,iOSDocuments/UserFonts同款設計)。- 儲存模型不變:專案照舊只存 PostScript 名——同一套字型 iPad 也裝,專案兩邊長一樣;
沒裝的機器回落黑體(與 iOS 匯入字型一致,字型檔不進專案檔)。 - 關鍵驗證=PS 名直接命中:canvas 對
"STSongti-TC-Regular"(PS 名)與"Songti TC"(家族名)量寬一致(407=407)且異於 fallback(416)——
系統字型不用 FontFace、不用 await,登記即用;「字型要先載完」那條雷只打 url() 來源,
匯入檔照舊 await。 - 別台電腦的專案帶了沒裝的字型:選單補「(未安裝)」項,不會靜靜跳回黑體。
- Rust 單元測試 2/2(枚舉 >50 套、PS 名唯一、內嵌 Inter 檔讀出正確 PS 名)。
- 儲存模型不變:專案照舊只存 PostScript 名——同一套字型 iPad 也裝,專案兩邊長一樣;
驗證:tsc ✓、自測 113/113、CDP(首頁三態+選單結構+Esc)、截圖(空狀態/有卡片)。
出版:1.0.4 已簽名公證(SHA e5e54866…),/Applications 已更新。
已驗證(2026-08-09 第二十四輪:濾鏡全滅——兩顆 Chrome 測不出的 WKWebView 雷)
小高回報 1.0.7「套濾鏡後預覽不顯示」。瀏覽器怎麼測都是好的(圖片+濾鏡 ✓、影片+濾鏡 ✓、
零錯誤)——問題只存在真 WKWebView。用「?probe=filter 探針+beacon 收端+devUrl 暫帶參數」
在 tauri dev 的真環境跑,一次揪出兩顆:
- asset:// 的圖在 WKWebView 會污染 canvas(Chrome 不會)。
filteredCanvas的getImageData直接SecurityError: The operation is insecure=圖片濾鏡 100% 壞。
連鎖:畫過 asset:// 圖的畫布都髒了,⌘E 匯出 PNG 對任何磁碟專案其實從 1.0.0 就是壞的
(之前匯出驗證都驗到影片頁 mp4,PNG 頁沒真環境測過)。 - crossOrigin 影片全黑:1.0.5 給
<video>加了crossOrigin="anonymous"(防污染,方向對),
但 WebKit 對帶 Range 的跨源媒體請求會先發 CORS preflight(OPTIONS)——媒體伺服器沒接
OPTIONS,整個載入判死(err 4 / SRC_NOT_SUPPORTED)=影片從 1.0.5 起在真 App 只剩海報不會動。
Chrome 測不出(dev server 同源,根本不走 CORS)。
修法=全媒體統一走 127.0.0.1 媒體伺服器、asset:// 退役:
- mediaserv 補整套 CORS:OPTIONS preflight(Allow-Headers 含 Range)、
所有回應(含 416/403/404)都帶 ACAO——WebKit 只要有一個回應缺頭就整條判死;
Expose-Headers 給 Content-Range;MIME 表補圖片與字型。 - 前端
localUrl()=磁碟路徑→媒體伺服器 URL 的唯一出口;convertFileSrc全數移除。<img>/<video>(含 capturePoster)對 http 來源一律crossOrigin="anonymous"。 - 使用者字型檔資料夾也註冊進媒體伺服器(FontFace 的 fetch 是強制 CORS)。
驗證(真 WKWebView+分鏡展示 15×4K):圖片濾鏡變體 ✓、海報變體 ✓、影片即時濾鏡影格 ✓
(修前連 plain 影格都是 false)、頁內 fetch 200/Range 206 ✓、自測 118/118。
探針留作常備診斷旗標(?probe=filter,配 ?diag=1 的 beacon)。
順帶發現(資料問題,與程式無關):
ClaudeForge/out/ALIGNED 說明輪播(8頁).alignproj
打包時沒把 aligned-guides.mp4 收進去(解包只有海報)——要用那份示範前先重打包。
已驗證(2026-08-09 第二十五輪:縮放卡=rAF 半死不活 25Hz,看門狗升級)
小高回報「有一點卡,縮放也會卡」。?probe=zoom 探針(2.5 秒正弦縮放掃描)在真
WKWebView+分鏡展示量到的數字推翻直覺:
- 單張重畫只要 0.6ms、主執行緒最大堵塞 1ms——成本毫無問題;
- 2.5 秒只畫了 64 張=約 25fps——卡的是拍率:這台 WKWebView 的 rAF
沒死(所以 90ms 看門狗不出手)、但也只跑 ~25Hz,畫面就以慢動作跟手。
修法=看門狗規則升級:從「rAF 心跳停了才代打」改成「有髒、且距上一次真正畫
超過 15ms 就補拍」(週期 8ms)。rAF 健康 60fps 時永遠輪不到它;慢心跳/死心跳
一律自動補滿。量測:64 → 94(門檻 25ms)→ 156 張/2.5 秒=62fps,
單張 0.9ms、主執行緒堵塞 2ms。影片播放的重畫節奏也一起受益(同一個 dirty 管線)。
自測 118/118。?probe=zoom 留作常備診斷旗標。
已驗證(2026-08-09 第二十六輪:「開著濾鏡會卡」——GPU 邊界大狩獵,VideoFrame 終局)
小高回報濾鏡開著會卡。?probe=filterlag(全影片套 a1、3 秒穩態)基準線:
單次轉檔堵主執行緒 125ms、規律如節拍器(每 125ms 一次);對照組(無濾鏡)乾淨如水
=毒 100% 在濾鏡路。接下來是一場「把影片像素送去做濾鏡」的載具淘汰賽——
主執行緒上凡是讓像素跨越 CPU↔GPU 邊界的操作,全部都是同步等 GPU 沖洗管線:
| 載具 | 主執行緒同步成本 | 結果 |
|---|---|---|
getImageData(畫完立刻讀) |
~110–170ms | ✗ |
createImageBitmap(video) |
453ms | ✗ |
GPU 位圖 postMessage 轉移給工人 |
155ms | ✗ |
getImageData 延後一拍再讀(GPU 該完工了吧?) |
還是 171ms | ✗ 理論被推翻 |
主緒 putImageData(512px) |
18ms | ✗ 連上屏都毒 |
new VideoFrame(video)(WebCodecs) |
1ms+轉移 1ms | ✅ 唯一的門 |
終局架構=主執行緒每格只做「包影格+寄出」(~2ms):new VideoFrame(el) → 轉移給濾鏡工人 → 工人 drawImage(vf→512)+getImageData
+applyFilter+putImageData,直接畫進 transferControlToOffscreen 移交過去的
顯示畫布——連結果都不運回主執行緒,渲染端對那張 DOM canvas drawImage 合成(0ms 級)。vf.close() 必須做(VideoFrame 是解碼器資源,不還會堵死解碼管線)。
量測(分鏡展示 15×4K、全部套濾鏡):主執行緒堵塞 121ms → 1ms、節拍器事件歸零,
濾鏡影格 66 → 89 格/秒(工人單格 123ms 全在工人執行緒燒,UI 無感)。
特性檢查(VideoFrame+transferControlToOffscreen+OffscreenCanvas)不過就退回同步舊路。
瀏覽器回歸:濾鏡像素真的變了、自測 118/118。
還沒做
- 跨頁續流(textFlow 長文框 v2)——iOS 主線也還沒出(feat/text-wrap 分支未合併),
等 iOS 定案再同步。 - 圖層清單(2026-08-05 已做:側欄面板,帶真縮圖、拖曳換前後、hover 鎖頭)
- 群組等比縮放手把(2026-08-07 已做,見下面第二十輪)
- 去背圖庫(Mac 直接把去背 PNG 拖進畫布即可,等效)、跨專案複製、
iCloud 傳輸資料夾(Mac 有 Finder,等效)、設定頁/多語/Pro 金流。 .alignproj重新打包的 iPad 匯入實測(只有 Armin 能做)- 工具間下載頁+更新 feed(✅ 2026-08-08 上線,見下面〈發版流程〉)
(1.0.0 已簽名公證出貨:release/ALIGNED_1.0.0_aarch64.dmg,發版一鍵 scripts/release-mac.sh。
新增/刪除元件、存檔、undo、拖放匯入、行內編輯都已在 1.0.0。)
發版流程(2026-08-10 起改走 GitHub Releases,剩兩步+順手一步)
- 出版:兩處版本號同步升(
package.json+src-tauri/tauri.conf.json)→npx tauri build --bundles app→scripts/release-mac.sh。
腳本現在一路做完:簽名→公證→DMG→驗收→開 Release→上傳 asset→抓回來比對 SHA。 - 餵 feed:改
37 - 工具間/上線/aligned-latest.json的version與notes→ push。url不用動了,它是永久網址。App 開機查這份 feed 比版本後浮更新橫幅
(src/updatecheck.ts,離線安靜跳過)。
⏱ 批次推(2026-08-14 拍板):Release 隨改隨出(新下載自動拿最新),
但 feed 一個工作段落推一次、一天最多一次,緊急修復才即時——
「略過此版」是按版本記的,feed 推太密會把使用者按過的「不要吵」洗掉。
notes 寫這批的總和,不是只寫最後一版。 - 順手更新小高本機(2026-08-14 定案,小高交代「以後都順便」):
掛載剛出爐的release/ALIGNED_<版本>_aarch64.dmg→ditto蓋掉/Applications/ALIGNED.app→ detach →xattr -dr com.apple.quarantine
→ 讀 Info.plist 驗版本+spctl -a驗簽。
兩個當天踩過的坑:- 程序名是
aligned-mac不是 ALIGNED——查沒在跑要用pgrep -f aligned-mac
(pgrep -x ALIGNED永遠回「沒在跑」,會在使用者開著 App 時蓋檔)。
在跑就先osascript -e 'tell application "ALIGNED" to quit'請它正常退出。 - DMG 帶著驗收蓋的
RELEASE-TESTquarantine 標記,ditto會原樣帶進
/Applications,之後open會被 Gatekeeper 搬去 AppTranslocation 跑——
所以裝完必清 quarantine。
- 程序名是
下載網址固定為https://github.com/qwert2813434-ctrl/ALIGNED/releases/latest/download/ALIGNED-macOS-arm64.dmg
——asset 用不帶版號的固定檔名,releases/latest/download/ 永遠導到最新版,
所以 README 連結與 feed 的 url 發新版都不用改。
為什麼從 raw/main/release/ 換過來(2026-08-10):
- raw 直連沒有任何下載計數,發了不知道成效;Release asset 有
download_count,還能分版本看。 - 每版 ~95MB 的 DMG 進 git 就永久留在歷史裡,換之前
.git已經 556MB。Release asset 不進 git。 - 固定檔名在 raw 上不可行(CDN 快取會給到舊檔),在 Releases 上可行(302 導到新 asset)。
🔴 release/ 裡的舊 DMG 凍結兜底,勿刪——1.0.11 之前發出去的 raw 直連還指著它們
(AutoDM 送的是 repo 首頁不是直連,所以私訊那條沒事,但 README 舊版本、書籤、轉貼都可能還在用)。.git 歷史裡那 556MB 也不要回頭清:要 rewrite history + force push,會弄壞別人已經 clone 的
副本,不划算。停止繼續加就好。
Windows 版怎麼出(2026-08-16 首發)
Mac 上編不出 Windows 包(缺 MSVC 與 llvm-rc),走 GitHub Actions:推 win-build
分支就自動跑 .github/workflows/windows-build.yml(windows-latest → npx tauri build --bundles nsis),artifact 抓下來就是安裝檔。改名成 ALIGNED-windows-x64-setup.exe
掛到當版 Release,README 那條固定連結就自動指到它。
平台差異全部收在 src-tauri/tauri.windows.conf.json(Tauri v2 自動合併,Mac 端的tauri.conf.json 一個字都不用動):NSIS 包裝、.ico、resources 清空(不打包
alignvideo)、beforeBuildCommand 改走 scripts/prune-dist.mjs(Swift 的videotool/build.sh 只長在 Mac,rm -rf 在 Windows 也不存在)、mainBinaryName: ALIGNED(不設的話裝出來叫 aligned-mac.exe,Cargo 套件名會漏出去)。
🔴 未簽章:SmartScreen 會擋一次。要根治得買憑證,先在 README 寫明繞法。
驗收怎麼做:測試筆電的互動桌面搆不到(SSH 落在 session 0),所以用
Windows Edge 跑 selftest.html——Edge 與 WebView2 同一個 Chromium,
149/149 通過就等於編輯核心在 Windows 成立。作法:Mac 開 npm run dev -- --host,
Windows 端 headless Edge 開 --remote-debugging-port,SSH -L 打洞回來用 CDP 取結果。
倉庫紅線:public/samples/real/(個人照片)永不入 repo(.gitignore 擋著);
新增內嵌資源前先想授權(字型清單在 public/fonts/LICENSES.md)。
貫穿全程的約束:畫布/頁面膠捲/屬性檢視器三塊面板要獨立成元件,
版面由尺寸級距決定,絕不把手機版面寫死再回頭拆(摺疊機與大螢幕適配的地基)。
移植時的地雷
- 字型必須 await 載完再畫。canvas 對還沒載入的字型會靜默回落系統字型,
不報錯、只是量測與渲染全錯。 letterSpacing每個字後面都加,包含最後一個——量到的寬度多一個字距,
置中會偏半個、靠右不貼邊。render.ts的lineWidth()已減回去
(iOS 端對應KernApplier.applyStrippingTrailing)。cropRect的(0,0,1,1)不可照字面拉伸,語意是「未曾裁切」=置中 aspect-fill,
照字面拉會讓換圖後變形。見geometry.aspectFillCrop。- 文字顏色以 AttributedString 的 run 屬性優先,
colorHex只是後備。
外部產生的 JSON 若只給colorHex,深色模式會變白字、白底頁直接隱形。 canvasWidth不保證是 1080——短邊才是 1080,橫式 16:9 專案是 1920×1080。
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found