dsh-notch 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BASELINE.md +13 -0
- package/DESIGN.md +34 -0
- package/LICENSE +21 -0
- package/README.en.md +136 -0
- package/README.md +145 -0
- package/cordis.yml +5 -0
- package/docs/motion-audit.md +65 -0
- package/docs/motion-continuity.md +77 -0
- package/docs/releases/v0.3.0.md +20 -0
- package/dshx.yml +14 -0
- package/macos/Package.swift +17 -0
- package/macos/STATUS-MOTION.md +49 -0
- package/macos/Sources/Client.swift +139 -0
- package/macos/Sources/IdleRobot.swift +423 -0
- package/macos/Sources/NotchMarkdown.swift +266 -0
- package/macos/Sources/Panel.swift +153 -0
- package/macos/Sources/Resources/Idle/balance.json +1 -0
- package/macos/Sources/Resources/Idle/blink.json +1 -0
- package/macos/Sources/Resources/Idle/cube-in.json +1 -0
- package/macos/Sources/Resources/Idle/dance.json +1 -0
- package/macos/Sources/Resources/Idle/hop.json +1 -0
- package/macos/Sources/Resources/Idle/nod.json +1 -0
- package/macos/Sources/Resources/Idle/satellite-in.json +1 -0
- package/macos/Sources/Resources/Idle/satellite-out.json +1 -0
- package/macos/Sources/Resources/Idle/scan.json +1 -0
- package/macos/Sources/Resources/Idle/sleep.json +1 -0
- package/macos/Sources/Resources/Idle/sneeze.json +1 -0
- package/macos/Sources/Resources/Idle/stretch.json +1 -0
- package/macos/Sources/Resources/Idle/tilt.json +1 -0
- package/macos/Sources/ReviewStudio.swift +337 -0
- package/macos/Sources/RootView.swift +1093 -0
- package/macos/Sources/StatusOrbit.swift +642 -0
- package/macos/Sources/main.swift +153 -0
- package/macos/Tests/ExpandedHeightProbe.swift +108 -0
- package/macos/Tests/GeometryProbe.swift +98 -0
- package/macos/Tests/IdleProbe.swift +294 -0
- package/macos/Tests/MotionProbe.swift +106 -0
- package/macos/Tests/OutcomeProbe.swift +133 -0
- package/macos/Tests/ScrollbarProbe.swift +91 -0
- package/macos/Tests/expanded-height.sh +13 -0
- package/macos/Tests/geometry.sh +14 -0
- package/macos/Tests/idle.sh +14 -0
- package/macos/Tests/motion.sh +14 -0
- package/macos/Tests/outcome.sh +14 -0
- package/macos/Tests/scrollbar.sh +13 -0
- package/package.json +46 -0
- package/src/board.ts +299 -0
- package/src/browse-sync.ts +59 -0
- package/src/dsh-notch.ts +60 -0
- package/src/http.ts +215 -0
- package/src/session-state.ts +31 -0
- package/src/store.ts +49 -0
- package/src/types.ts +65 -0
- package/tests/answer-lifecycle.test.mjs +23 -0
- package/tests/board.test.mjs +7 -0
- package/tests/browse-sync.test.mjs +20 -0
- package/tests/mirror.test.mjs +4 -0
- package/tests/peer-range.test.mjs +21 -0
- package/tests/session-state.test.mjs +54 -0
- package/tests/unread-hold.test.mjs +90 -0
- package/tools/desktop-shell/README.md +56 -0
- package/tools/desktop-shell/child-log.mjs +6 -0
- package/tools/desktop-shell/child-log.test.mjs +14 -0
- package/tools/desktop-shell/package-recovery.py +78 -0
- package/tools/desktop-shell/recovery-probe.cjs +50 -0
- package/tools/desktop-shell/renderer-recovery.mjs +95 -0
- package/tools/desktop-shell/renderer-recovery.test.mjs +24 -0
- package/tools/diagnostics/completion-watch.py +101 -0
- package/tools/diagnostics/completion-watch.test.py +17 -0
- package/tools/diagnostics/renderer-diagnostics.mjs +56 -0
- package/tools/diagnostics/renderer-diagnostics.test.mjs +32 -0
- package/tools/idle/LICENSE.open-bot-motion +21 -0
- package/tools/idle/export.mjs +40 -0
- package/tools/idle/index.html +4 -0
- package/tools/idle/motions.js +83 -0
- package/tools/idle/original.js +1673 -0
- package/tools/idle/renderer.js +1673 -0
- package/tools/recording/Client.swift +76 -0
- package/tools/recording/DemoCatalog.swift +174 -0
- package/tools/recording/README.md +23 -0
- package/tools/recording/build.sh +41 -0
- package/tools/recording/main.swift +100 -0
- package/tools/review/ReviewStudio.swift +236 -0
- package/tools/review/audit.sh +19 -0
- package/tools/review/build.sh +7 -0
- package/tools/review/main.swift +158 -0
- package/tools/review/render-audit.swift +32 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
repo=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
|
4
|
+
export DEVELOPER_DIR="${DEVELOPER_DIR:-/Library/Developer/CommandLineTools}"
|
|
5
|
+
export SDKROOT="${SDKROOT:-$(xcrun --show-sdk-path)}"
|
|
6
|
+
swift build --package-path "$repo/macos" -c release
|
|
7
|
+
exec "$repo/macos/.build/release/dsh-notch" --demo
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
enum ReviewCase:Int,CaseIterable,Identifiable {
|
|
5
|
+
case idleWork,success,failure,lastSuccess,lastFailure,greenIdle,redIdle,idleDecision,workDecision,decisionWork,decisionIdle,mixed,approval,idleTour
|
|
6
|
+
var id:Int { rawValue }
|
|
7
|
+
var title:String {
|
|
8
|
+
["01 机器人 → 运行","02 运行 → 成功(还有任务)","03 运行 → 失败(还有任务)","04 三灯 → 最后任务成功","05 三灯 → 最后任务失败","06 读掉最后绿点 → 机器人","07 读掉最后红点 → 机器人","08 机器人 → 黄色决策","09 运行 → 黄色决策","10 黄色决策 → 继续运行","11 黄色决策 → 待机","12 黄、蓝、绿、红混合 → 逐个清除","13 工具审批 → 继续运行","14 全部待机动作与变色龙"][rawValue]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
@MainActor final class LoopModel:ObservableObject {
|
|
12
|
+
@Published var board=BoardModel()
|
|
13
|
+
@Published var label="准备开始"
|
|
14
|
+
@Published var round=0
|
|
15
|
+
@Published var enlarged=true
|
|
16
|
+
@Published var selectedCase=ReviewCase.idleWork
|
|
17
|
+
@Published var auto=true
|
|
18
|
+
private var job:Task<Void,Never>?
|
|
19
|
+
private func rows(_ busy:Int=0,_ done:Int=0,_ failed:Int=0,_ decision:Bool=false,_ approval:Bool=false)->[NotchRow] {
|
|
20
|
+
var rows=(0..<busy).map{NotchRow(id:"work-\($0)",title:"本地模拟任务",child:false,busy:true,unread:false)}
|
|
21
|
+
rows += (0..<done).map{NotchRow(id:"done-\($0)",title:"本地模拟完成",child:false,busy:false,unread:true)}
|
|
22
|
+
rows += (0..<failed).map{NotchRow(id:"error-\($0)",title:"本地模拟失败",child:false,busy:false,unread:true,lastTurn:NotchLastTurn(at:0,kind:"error",failed:true))}
|
|
23
|
+
if decision || approval {
|
|
24
|
+
var question=NotchRow(id:"decision",title:"本地模拟决策",child:false,busy:false,unread:false)
|
|
25
|
+
if approval {question.approval=NotchApproval(id:"local-approval",toolName:"本地演示",reason:"不会执行任何真实操作")}
|
|
26
|
+
else {question.ask=NotchAsk(id:"local-question",questions:[NotchQuestion(id:"choice",question:"选择演示配色",options:[NotchOption(label:"深色"),NotchOption(label:"浅色")])])}
|
|
27
|
+
rows.append(question)
|
|
28
|
+
}
|
|
29
|
+
return rows
|
|
30
|
+
}
|
|
31
|
+
private func show(_ rows:[NotchRow],_ title:String) {
|
|
32
|
+
label=title;board.applySnapshot(NotchSnapshot(ok:true,generatedAt:0,origin:"local-review",rows:rows));board.expanded=false
|
|
33
|
+
}
|
|
34
|
+
private func wait(_ seconds:Double)async->Bool {
|
|
35
|
+
do{try await Task.sleep(for:.seconds(seconds));return !Task.isCancelled}catch{return false}
|
|
36
|
+
}
|
|
37
|
+
func start(){run(selectedCase)}
|
|
38
|
+
func run(_ item:ReviewCase){
|
|
39
|
+
job?.cancel();selectedCase=item
|
|
40
|
+
job=Task { @MainActor in
|
|
41
|
+
var current=item
|
|
42
|
+
repeat {
|
|
43
|
+
selectedCase=current;round+=1;board=BoardModel()
|
|
44
|
+
var initial:[NotchRow]=[]
|
|
45
|
+
switch current {
|
|
46
|
+
case .idleWork,.idleDecision,.idleTour:initial=[]
|
|
47
|
+
case .success,.failure:initial=rows(2)
|
|
48
|
+
case .lastSuccess,.lastFailure:initial=rows(1,1,1)
|
|
49
|
+
case .greenIdle:initial=rows(0,1,1)
|
|
50
|
+
case .redIdle:initial=rows(0,1,1)
|
|
51
|
+
case .workDecision:initial=rows(1)
|
|
52
|
+
case .decisionWork,.decisionIdle:initial=rows(0,0,0,true)
|
|
53
|
+
case .mixed:initial=rows(1,1,1,true)
|
|
54
|
+
case .approval:initial=rows(0,0,0,false,true)
|
|
55
|
+
}
|
|
56
|
+
show(initial,"起始状态")
|
|
57
|
+
guard await wait(1.3) else{return}
|
|
58
|
+
switch current {
|
|
59
|
+
case .idleTour:
|
|
60
|
+
let names=["blink":"眨眼回神","scan":"左右巡视","tilt":"歪头琢磨","nod":"点头招呼","stretch":"伸个懒腰","hop":"果冻小跳","balance":"摇摇平衡","sneeze":"憋个喷嚏","sleep":"打盹 · 闭眼约 4 秒","dance":"变色龙彩蛋"]
|
|
61
|
+
for id in ["dance"]+IdleDirector.basics {
|
|
62
|
+
guard let director=IdleDirector.previewInstance else {return}
|
|
63
|
+
director.automaticActions=false;director.play(id)
|
|
64
|
+
label=names[id] ?? id
|
|
65
|
+
let duration=director.currentDuration
|
|
66
|
+
guard await wait(duration) else{return}
|
|
67
|
+
director.tick(Date())
|
|
68
|
+
let rest=IdleDirector.restDuration()
|
|
69
|
+
label="普通待机 · 自然眨眼 · \(Int(rest.rounded())) 秒"
|
|
70
|
+
guard await wait(rest) else{return}
|
|
71
|
+
}
|
|
72
|
+
case .idleWork:
|
|
73
|
+
IdleDirector.previewInstance?.play("balance")
|
|
74
|
+
guard await wait(1.7) else{return}
|
|
75
|
+
show(rows(1),"后仰缩小 → 蓝圈和数字")
|
|
76
|
+
case .success,.failure,.lastSuccess,.lastFailure:
|
|
77
|
+
var next=initial
|
|
78
|
+
next[0].busy=false;next[0].unread=true
|
|
79
|
+
if current == .failure || current == .lastFailure {next[0].lastTurn=NotchLastTurn(at:0,kind:"error",failed:true)}
|
|
80
|
+
show(next,"笔划送出结果 · 外壳同步")
|
|
81
|
+
case .greenIdle:
|
|
82
|
+
show(rows(0,1),"先读掉红点 · 只剩绿点")
|
|
83
|
+
guard await wait(1.6) else{return}
|
|
84
|
+
show([],"读掉最后绿点 → cube 旋转出场")
|
|
85
|
+
case .redIdle:
|
|
86
|
+
show(rows(0,0,1),"先读掉绿点 · 只剩红点")
|
|
87
|
+
guard await wait(1.6) else{return}
|
|
88
|
+
show([],"读掉最后红点 → cube 旋转出场")
|
|
89
|
+
case .idleDecision,.workDecision:show(rows(0,0,0,true),"需要用户决定 → 黄色 !")
|
|
90
|
+
case .decisionWork,.approval:show(rows(1),"已作决定 → 蓝色运行")
|
|
91
|
+
case .decisionIdle:show([],"取消决策 → 待机")
|
|
92
|
+
case .mixed:
|
|
93
|
+
show(rows(1,1,1),"决策已处理 · 黄灯消退")
|
|
94
|
+
guard await wait(1.6) else{return}
|
|
95
|
+
show(rows(1,0,1),"读掉绿点")
|
|
96
|
+
guard await wait(1.6) else{return}
|
|
97
|
+
show(rows(1),"读掉红点 · 仍有任务")
|
|
98
|
+
}
|
|
99
|
+
guard await wait(3.6) else{return}
|
|
100
|
+
if !auto {label+=" · 可重播或切换案例";return}
|
|
101
|
+
current=ReviewCase(rawValue:(current.rawValue+1)%ReviewCase.allCases.count)!
|
|
102
|
+
} while !Task.isCancelled
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
func next(){run(ReviewCase(rawValue:(selectedCase.rawValue+1)%ReviewCase.allCases.count)!)}
|
|
106
|
+
func stop(){job?.cancel();job=nil}
|
|
107
|
+
}
|
|
108
|
+
struct LoopView: View {
|
|
109
|
+
@ObservedObject var loop: LoopModel
|
|
110
|
+
var body: some View {
|
|
111
|
+
VStack(spacing:16) {
|
|
112
|
+
Text("Notch 转场审阅 · \(loop.selectedCase.rawValue+1) / \(ReviewCase.allCases.count)").font(.system(size:15,weight:.medium)).foregroundStyle(.primary)
|
|
113
|
+
Picker("审阅案例",selection:Binding(get:{loop.selectedCase},set:{loop.run($0)} )) {
|
|
114
|
+
ForEach(ReviewCase.allCases){item in Text(item.title).tag(item)}
|
|
115
|
+
}
|
|
116
|
+
Text(loop.label).font(.system(size:13)).foregroundStyle(.secondary)
|
|
117
|
+
LoopRobot(board:loop.board,enlarged:loop.enlarged).frame(width:200,height:390,alignment:.top)
|
|
118
|
+
HStack {
|
|
119
|
+
Button(loop.enlarged ? "实际大小" : "放大 3 倍") {loop.enlarged.toggle()}
|
|
120
|
+
Button("重播本项") {loop.start()}
|
|
121
|
+
Button("下一项") {loop.next()}
|
|
122
|
+
Toggle("连续播放",isOn:$loop.auto).toggleStyle(.checkbox)
|
|
123
|
+
Button("结束预览") {loop.stop();NSApplication.shared.terminate(nil)}
|
|
124
|
+
}
|
|
125
|
+
Text("本地模拟 · 无模型调用 · 不修改真实任务").font(.system(size:11)).foregroundStyle(.secondary)
|
|
126
|
+
}.padding(24).frame(width:620,height:720)
|
|
127
|
+
.background(.regularMaterial,in:RoundedRectangle(cornerRadius:22))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
struct LoopRobot:View {
|
|
131
|
+
@ObservedObject var board:BoardModel
|
|
132
|
+
let enlarged:Bool
|
|
133
|
+
var body:some View {
|
|
134
|
+
RootView(model:board,panelSize:CGSize(width:320,height:460),restSize:CGSize(width:38,height:44))
|
|
135
|
+
.frame(width:board.currentIslandWidth,height:board.currentIslandHeight)
|
|
136
|
+
.animation(board.expanded ? NotchGeometryAnimation.animation : nil, value: CGSize(width:board.currentIslandWidth,height:board.currentIslandHeight))
|
|
137
|
+
.scaleEffect(enlarged ? 3 : 1,anchor:.top)
|
|
138
|
+
.allowsHitTesting(false)
|
|
139
|
+
.id(ObjectIdentifier(board))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
@main enum LoopMain {
|
|
143
|
+
@MainActor static func main(){
|
|
144
|
+
let app=NSApplication.shared;app.setActivationPolicy(.regular)
|
|
145
|
+
let delegate=LoopDelegate();app.delegate=delegate;app.run()
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
@MainActor final class LoopDelegate:NSObject,NSApplicationDelegate,NSWindowDelegate {
|
|
149
|
+
let loop=LoopModel();var panel:NSWindow?
|
|
150
|
+
func applicationDidFinishLaunching(_ notification:Notification){
|
|
151
|
+
let window=NSWindow(contentRect:NSRect(x:0,y:0,width:620,height:720),styleMask:[.titled,.closable],backing:.buffered,defer:false)
|
|
152
|
+
window.delegate=self;window.title="Notch 独立循环预览";window.isOpaque=false;window.backgroundColor = .clear;window.hasShadow=true;window.level = .floating
|
|
153
|
+
window.isMovableByWindowBackground=true;window.contentView=NSHostingView(rootView:LoopView(loop:loop));window.center();window.makeKeyAndOrderFront(nil);appActivate();panel=window;loop.start()
|
|
154
|
+
}
|
|
155
|
+
func appActivate(){NSApplication.shared.activate(ignoringOtherApps:true)}
|
|
156
|
+
func windowWillClose(_ notification:Notification){loop.stop();NSApplication.shared.terminate(nil)}
|
|
157
|
+
func applicationWillTerminate(_ notification:Notification){loop.stop()}
|
|
158
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
struct AuditSurface:View {
|
|
2
|
+
@ObservedObject var loop:LoopModel
|
|
3
|
+
var body:some View {
|
|
4
|
+
LoopRobot(board:loop.board,enlarged:true).frame(width:200,height:390,alignment:.top).background(Color(white:0.15))
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
@main enum AuditMain {
|
|
8
|
+
@MainActor static func main(){
|
|
9
|
+
let app=NSApplication.shared;app.setActivationPolicy(.accessory)
|
|
10
|
+
let loop=LoopModel();loop.auto=false
|
|
11
|
+
let host=NSHostingView(rootView:AuditSurface(loop:loop));host.frame=NSRect(x:0,y:0,width:200,height:390)
|
|
12
|
+
let panel=NSPanel(contentRect:host.frame,styleMask:[.borderless],backing:.buffered,defer:false);panel.contentView=host
|
|
13
|
+
panel.setFrameOrigin(NSPoint(x:-3000,y:-3000));panel.orderFrontRegardless()
|
|
14
|
+
let base=URL(fileURLWithPath:ProcessInfo.processInfo.environment["NOTCH_AUDIT_OUTPUT"]!)
|
|
15
|
+
Task { @MainActor in
|
|
16
|
+
for item in ReviewCase.allCases where (ProcessInfo.processInfo.environment["NOTCH_AUDIT_CASES"].map { $0.split(separator:",").contains(Substring(String(item.rawValue+1))) } ?? true) {
|
|
17
|
+
let out=base.appendingPathComponent(String(format:"%02d",item.rawValue+1));try! FileManager.default.createDirectory(at:out,withIntermediateDirectories:true)
|
|
18
|
+
loop.run(item)
|
|
19
|
+
let frameCount=item == .idleTour ? 2600:180
|
|
20
|
+
for i in 0..<frameCount {
|
|
21
|
+
try? await Task.sleep(for:.milliseconds(50))
|
|
22
|
+
host.layoutSubtreeIfNeeded();host.displayIfNeeded()
|
|
23
|
+
let rep=host.bitmapImageRepForCachingDisplay(in:host.bounds)!;host.cacheDisplay(in:host.bounds,to:rep)
|
|
24
|
+
try! rep.representation(using:.png,properties:[:])!.write(to:out.appendingPathComponent(String(format:"%03d.png",i)))
|
|
25
|
+
}
|
|
26
|
+
print("CAPTURED \(item.title) \(frameCount) frames")
|
|
27
|
+
}
|
|
28
|
+
loop.stop();print("CAPTURE_COMPLETE");exit(0)
|
|
29
|
+
}
|
|
30
|
+
app.run()
|
|
31
|
+
}
|
|
32
|
+
}
|