dsh-notch 0.3.1 → 0.3.4
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/README.en.md +58 -47
- package/README.md +60 -55
- package/cordis.patch.yml +3 -0
- package/desktop/README.md +29 -0
- package/desktop/notch-lifecycle.mjs +120 -0
- package/desktop/restart-probe.mjs +61 -0
- package/docs/desktop-017rc2.md +23 -0
- package/docs/elastic-edge-dock.md +127 -0
- package/docs/motion-audit.md +1 -1
- package/docs/releases/v0.3.2.md +38 -0
- package/docs/releases/v0.3.3.md +9 -0
- package/docs/subagent-compatibility.md +51 -0
- package/docs/verification/elastic-algorithm-timing.json +337 -0
- package/docs/verification/elastic-attention.json +44 -0
- package/docs/verification/elastic-frame-timing.json +240 -0
- package/docs/verification/elastic-tension.json +59 -0
- package/docs/verification/elastic-tip-taper.json +181 -0
- package/dshx.yml +2 -0
- package/lib/client.js +112 -0
- package/lib/client.js.map +1 -0
- package/lib/index.mjs +990 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +58 -10
- package/src/board.ts +178 -35
- package/src/client/index.ts +104 -0
- package/src/dsh-notch.ts +19 -2
- package/src/http.ts +17 -2
- package/src/session-state.ts +15 -1
- package/src/store.ts +3 -3
- package/src/types.ts +3 -0
- package/BASELINE.md +0 -13
- package/DESIGN.md +0 -34
- package/cordis.yml +0 -5
- package/macos/Package.swift +0 -17
- package/macos/STATUS-MOTION.md +0 -49
- package/macos/Sources/Client.swift +0 -139
- package/macos/Sources/IdleRobot.swift +0 -423
- package/macos/Sources/NotchMarkdown.swift +0 -266
- package/macos/Sources/Panel.swift +0 -153
- package/macos/Sources/Resources/Idle/balance.json +0 -1
- package/macos/Sources/Resources/Idle/blink.json +0 -1
- package/macos/Sources/Resources/Idle/cube-in.json +0 -1
- package/macos/Sources/Resources/Idle/dance.json +0 -1
- package/macos/Sources/Resources/Idle/hop.json +0 -1
- package/macos/Sources/Resources/Idle/nod.json +0 -1
- package/macos/Sources/Resources/Idle/satellite-in.json +0 -1
- package/macos/Sources/Resources/Idle/satellite-out.json +0 -1
- package/macos/Sources/Resources/Idle/scan.json +0 -1
- package/macos/Sources/Resources/Idle/sleep.json +0 -1
- package/macos/Sources/Resources/Idle/sneeze.json +0 -1
- package/macos/Sources/Resources/Idle/stretch.json +0 -1
- package/macos/Sources/Resources/Idle/tilt.json +0 -1
- package/macos/Sources/ReviewStudio.swift +0 -337
- package/macos/Sources/RootView.swift +0 -1093
- package/macos/Sources/StatusOrbit.swift +0 -642
- package/macos/Sources/main.swift +0 -153
- package/macos/Tests/ExpandedHeightProbe.swift +0 -108
- package/macos/Tests/GeometryProbe.swift +0 -98
- package/macos/Tests/IdleProbe.swift +0 -294
- package/macos/Tests/MotionProbe.swift +0 -106
- package/macos/Tests/OutcomeProbe.swift +0 -133
- package/macos/Tests/ScrollbarProbe.swift +0 -91
- package/macos/Tests/expanded-height.sh +0 -13
- package/macos/Tests/geometry.sh +0 -14
- package/macos/Tests/idle.sh +0 -14
- package/macos/Tests/motion.sh +0 -14
- package/macos/Tests/outcome.sh +0 -14
- package/macos/Tests/scrollbar.sh +0 -13
- package/tests/answer-lifecycle.test.mjs +0 -23
- package/tests/board.test.mjs +0 -7
- package/tests/browse-sync.test.mjs +0 -20
- package/tests/mirror.test.mjs +0 -4
- package/tests/peer-range.test.mjs +0 -21
- package/tests/session-state.test.mjs +0 -54
- package/tests/unread-hold.test.mjs +0 -90
- package/tools/desktop-shell/README.md +0 -56
- package/tools/desktop-shell/child-log.mjs +0 -6
- package/tools/desktop-shell/child-log.test.mjs +0 -14
- package/tools/desktop-shell/package-recovery.py +0 -78
- package/tools/desktop-shell/recovery-probe.cjs +0 -50
- package/tools/desktop-shell/renderer-recovery.mjs +0 -95
- package/tools/desktop-shell/renderer-recovery.test.mjs +0 -24
- package/tools/diagnostics/completion-watch.py +0 -101
- package/tools/diagnostics/completion-watch.test.py +0 -17
- package/tools/diagnostics/renderer-diagnostics.mjs +0 -56
- package/tools/diagnostics/renderer-diagnostics.test.mjs +0 -32
- package/tools/idle/LICENSE.open-bot-motion +0 -21
- package/tools/idle/export.mjs +0 -40
- package/tools/idle/index.html +0 -4
- package/tools/idle/motions.js +0 -83
- package/tools/idle/original.js +0 -1673
- package/tools/idle/renderer.js +0 -1673
- package/tools/recording/Client.swift +0 -76
- package/tools/recording/DemoCatalog.swift +0 -174
- package/tools/recording/README.md +0 -23
- package/tools/recording/build.sh +0 -41
- package/tools/recording/main.swift +0 -100
- package/tools/review/ReviewStudio.swift +0 -236
- package/tools/review/audit.sh +0 -19
- package/tools/review/build.sh +0 -7
- package/tools/review/main.swift +0 -158
- package/tools/review/render-audit.swift +0 -32
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import AppKit
|
|
2
|
-
import SwiftUI
|
|
3
|
-
@main enum MotionProbe {
|
|
4
|
-
@MainActor static func main() {
|
|
5
|
-
let app=NSApplication.shared
|
|
6
|
-
app.setActivationPolicy(.accessory)
|
|
7
|
-
let output = URL(fileURLWithPath: ProcessInfo.processInfo.environment["NOTCH_MOTION_OUTPUT"] ?? NSTemporaryDirectory()).appendingPathComponent("notch-motion-proof")
|
|
8
|
-
try! FileManager.default.createDirectory(at: output, withIntermediateDirectories: true)
|
|
9
|
-
var failures=0
|
|
10
|
-
func check(_ value:Bool,_ label:String) { if !value { failures += 1; print("FAIL \(label)") } }
|
|
11
|
-
func snapshot(_ busy:Int,_ done:Int,_ failed:Int) -> NotchSnapshot {
|
|
12
|
-
let rows=(0..<busy).map { NotchRow(id:"busy-\($0)",title:"running",child:false,busy:true,unread:false) }
|
|
13
|
-
+ (0..<done).map { NotchRow(id:"done-\($0)",title:"done",child:false,busy:false,unread:true) }
|
|
14
|
-
+ (0..<failed).map { NotchRow(id:"failed-\($0)",title:"failed",child:false,busy:false,unread:true,lastTurn:NotchLastTurn(at:0,kind:"error",failed:true)) }
|
|
15
|
-
return NotchSnapshot(ok:true,generatedAt:0,origin:"local-test",rows:rows)
|
|
16
|
-
}
|
|
17
|
-
for failed in [false,true] {
|
|
18
|
-
for angle in stride(from: 0.0, to: 2 * Double.pi, by: Double.pi / 4) {
|
|
19
|
-
let route = OrbitBrushRoute(failed: failed, angle: angle)
|
|
20
|
-
check(route.points.allSatisfy { abs($0.x) + 0.75 <= 15 }, "brush stays inside shell")
|
|
21
|
-
for returns in [false,true] {
|
|
22
|
-
var previous = OrbitMotionFrame(progress:0,failed:failed,returns:returns,angle:angle)
|
|
23
|
-
check(previous.tint==0 && previous.resultOpacity==0,"blue start")
|
|
24
|
-
for i in 1...160 {
|
|
25
|
-
let frame = OrbitMotionFrame(progress:Double(i)/160,failed:failed,returns:returns,angle:angle)
|
|
26
|
-
check(frame.distance >= previous.distance,"brush head never reverses")
|
|
27
|
-
check(abs(frame.distance-previous.distance)<5,"continuous brush travel")
|
|
28
|
-
check(abs(frame.tint-previous.tint)<0.15,"continuous colour")
|
|
29
|
-
let range = route.range(for:frame)
|
|
30
|
-
check(range.lowerBound >= 0 && range.upperBound <= route.total + 0.01,"trail range")
|
|
31
|
-
previous=frame
|
|
32
|
-
}
|
|
33
|
-
check(previous.resultOpacity==1,"solid result appears")
|
|
34
|
-
check(returns ? previous.tint==0 : previous.tint==1,"final colour")
|
|
35
|
-
check(returns ? previous.tailLength>35 : previous.tailLength<0.01,"returning arc or settled dot")
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
let model=BoardModel()
|
|
40
|
-
var snap=snapshot(2,0,0);model.applySnapshot(snap)
|
|
41
|
-
check(model.statusFlight==nil,"cold snapshot has no flight")
|
|
42
|
-
snap.rows[0].busy=false;snap.rows[0].unread=true;model.applySnapshot(snap)
|
|
43
|
-
check(model.statusFlight?.failed==false,"successful completion moves upward")
|
|
44
|
-
let first=model.statusFlight!.id
|
|
45
|
-
model.applySnapshot(snap);check(model.statusFlight?.id==first,"polling does not replay")
|
|
46
|
-
snap.rows[1].busy=false;snap.rows[1].unread=true;snap.rows[1].lastTurn=NotchLastTurn(at:0,kind:"error",failed:true);model.applySnapshot(snap)
|
|
47
|
-
check(model.statusFlight?.id==first,"second outcome does not interrupt flight")
|
|
48
|
-
model.finishStatusFlight(id:first)
|
|
49
|
-
check(model.statusFlight?.failed==true,"failure queued next")
|
|
50
|
-
model.finishStatusFlight(id:first)
|
|
51
|
-
check(model.statusFlight?.failed==true,"stale completion cannot clear new flight")
|
|
52
|
-
model.finishStatusFlight(id:model.statusFlight!.id)
|
|
53
|
-
check(model.statusFlight==nil && model.busyCount==0 && model.completedUnreadCount==1 && model.failedRows.count==1,"final counts")
|
|
54
|
-
let panel=NotchPanel(size:NSSize(width:60,height:110))
|
|
55
|
-
let host=NotchHostingView(rootView:StatusOrbitView(model:model, reduceMotionOverride:false).frame(width:60,height:110).background(Color.black))
|
|
56
|
-
host.sizingOptions=[];panel.contentView=host
|
|
57
|
-
panel.setFrame(NSRect(x:-2000,y:-2000,width:60,height:110),display:true)
|
|
58
|
-
panel.orderFrontRegardless()
|
|
59
|
-
Task { @MainActor in
|
|
60
|
-
for failed in [false,true] {
|
|
61
|
-
model.rows=snapshot(1,failed ? 0:1,failed ? 1:0).rows
|
|
62
|
-
for (i,phase) in [0.0,0.12,0.24,0.38,0.50,0.62,0.76,0.90,0.99].enumerated() {
|
|
63
|
-
model.statusFlight=StatusFlight(failed:failed,startedAt:Date().addingTimeInterval(-phase*StatusFlight.duration),busyBefore:2,destinationBefore:0)
|
|
64
|
-
try? await Task.sleep(for:.milliseconds(16))
|
|
65
|
-
host.layoutSubtreeIfNeeded()
|
|
66
|
-
if let rep=host.bitmapImageRepForCachingDisplay(in:host.bounds) {
|
|
67
|
-
host.cacheDisplay(in:host.bounds,to:rep)
|
|
68
|
-
try! rep.representation(using:.png,properties:[:])!.write(to:URL(fileURLWithPath:"\(output.path)/\(failed ? "failure":"success")-\(i).png"))
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for failed in [false,true] {
|
|
73
|
-
model.statusFlight=nil
|
|
74
|
-
var replay=snapshot(2,0,0)
|
|
75
|
-
model.applySnapshot(replay)
|
|
76
|
-
replay.rows[0].busy=false;replay.rows[0].unread=true
|
|
77
|
-
if failed { replay.rows[0].lastTurn=NotchLastTurn(at:0,kind:"error",failed:true) }
|
|
78
|
-
model.applySnapshot(replay)
|
|
79
|
-
for i in 0..<100 {
|
|
80
|
-
try? await Task.sleep(for:.milliseconds(16))
|
|
81
|
-
host.layoutSubtreeIfNeeded()
|
|
82
|
-
if let rep=host.bitmapImageRepForCachingDisplay(in:host.bounds) {
|
|
83
|
-
host.cacheDisplay(in:host.bounds,to:rep)
|
|
84
|
-
let path="\(output.path)/replay-\(failed ? "failure":"success")-\(String(format:"%03d",i)).png"
|
|
85
|
-
try! rep.representation(using:.png,properties:[:])!.write(to:URL(fileURLWithPath:path))
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
model.statusFlight=nil;model.updateOrbitLayout();model.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
90
|
-
host.rootView = StatusOrbitView(model:model, reduceMotionOverride:true).frame(width:60,height:110).background(Color.black)
|
|
91
|
-
try? await Task.sleep(for:.milliseconds(60))
|
|
92
|
-
@MainActor func pixels() -> Data {
|
|
93
|
-
host.layoutSubtreeIfNeeded()
|
|
94
|
-
let rep=host.bitmapImageRepForCachingDisplay(in:host.bounds)!
|
|
95
|
-
host.cacheDisplay(in:host.bounds,to:rep)
|
|
96
|
-
return rep.representation(using:.png,properties:[:])!
|
|
97
|
-
}
|
|
98
|
-
let firstPixels=pixels()
|
|
99
|
-
try? await Task.sleep(for:.milliseconds(160))
|
|
100
|
-
check(firstPixels==pixels(),"reduced motion stays still")
|
|
101
|
-
try! firstPixels.write(to:URL(fileURLWithPath:"\(output.path)/reduced-motion.png"))
|
|
102
|
-
print("FAILURES=\(failures)");exit(failures==0 ? 0:1)
|
|
103
|
-
}
|
|
104
|
-
app.run()
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import AppKit
|
|
2
|
-
import SwiftUI
|
|
3
|
-
|
|
4
|
-
@main enum OutcomeProbe {
|
|
5
|
-
@MainActor static func main() {
|
|
6
|
-
NSApplication.shared.setActivationPolicy(.accessory)
|
|
7
|
-
var failures=0
|
|
8
|
-
func check(_ value:Bool,_ label:String) { if !value { failures += 1; print("FAIL \(label)") } }
|
|
9
|
-
for count in [1,2] { for kind in ["approval","success","failure"] {
|
|
10
|
-
let model=BoardModel();model.previewMode=true
|
|
11
|
-
var rows=(0..<count).map { NotchRow(id:"fixture-\($0)",title:"Local fixture",child:false,busy:true,unread:false) }
|
|
12
|
-
func snap()->NotchSnapshot {NotchSnapshot(ok:true,generatedAt:0,origin:"offline",rows:rows)}
|
|
13
|
-
model.applySnapshot(snap());model.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
14
|
-
check(model.statusFlight == nil,"cold state has no result replay")
|
|
15
|
-
if kind == "approval" {rows[0].approval=NotchApproval(id:"local-approval",toolName:"offline")}
|
|
16
|
-
else {rows[0].busy=false;rows[0].unread=true;rows[0].lastTurn=NotchLastTurn(at:1,kind:kind,failed:kind == "failure")}
|
|
17
|
-
model.applySnapshot(snap())
|
|
18
|
-
guard let flight=model.statusFlight else {check(false,"\(count) task \(kind): real snapshot must start travelling brush");continue}
|
|
19
|
-
check(flight.outcome == (kind == "approval" ? .decision:(kind == "failure" ? .failure:.success)),"\(kind): correct destination colour")
|
|
20
|
-
check(flight.returnsToRunning == (count > 1),"\(count) task \(kind): return only if work remains")
|
|
21
|
-
check(flight.busyBefore==count,"source count retained until departure")
|
|
22
|
-
let id=flight.id;model.applySnapshot(snap());check(model.statusFlight?.id==id,"polling does not restart brush")
|
|
23
|
-
let start=OrbitMotionFrame(progress:0,failed:flight.failed,returns:flight.returnsToRunning,angle:flight.angle)
|
|
24
|
-
check(start.tint==0 && start.resultOpacity==0 && start.tailLength>20,"\(kind): starts as visible blue arc")
|
|
25
|
-
let draw=OrbitMotionFrame(progress:0.72,failed:flight.failed,returns:flight.returnsToRunning,angle:flight.angle)
|
|
26
|
-
check(draw.tailLength>0,"\(kind): drawing phase contains ink")
|
|
27
|
-
let end=OrbitMotionFrame(progress:1,failed:flight.failed,returns:flight.returnsToRunning,angle:flight.angle)
|
|
28
|
-
check(end.resultOpacity==1,"\(kind): result is solid at end")
|
|
29
|
-
check(count > 1 ? end.tint==0 : end.tailLength==0,"\(kind): returning blue or settled result")
|
|
30
|
-
model.finishStatusFlight(id:id)
|
|
31
|
-
check(model.statusFlight==nil,"\(kind): flight ends")
|
|
32
|
-
check(model.busyCount==count-1,"\(kind): exact remaining work")
|
|
33
|
-
check(kind == "approval" ? model.needsAction : (kind == "failure" ? model.failedRows.count==1 : model.completedUnreadCount==1),"\(kind): final result retained")
|
|
34
|
-
print("CHECKED \(count) task \(kind)")
|
|
35
|
-
}}
|
|
36
|
-
func busyRows(_ count:Int)->[NotchRow] {(0..<count).map {NotchRow(id:"action-\($0)",title:"Local",child:false,busy:true,unread:false)}}
|
|
37
|
-
func snapshot(_ rows:[NotchRow])->NotchSnapshot {NotchSnapshot(ok:true,generatedAt:0,origin:"offline",rows:rows)}
|
|
38
|
-
for cancel in [false,true] {
|
|
39
|
-
let m=BoardModel();var rows=busyRows(1)
|
|
40
|
-
m.applySnapshot(snapshot(rows));m.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
41
|
-
rows[0].approval=NotchApproval(id:"approval",toolName:"offline")
|
|
42
|
-
m.applySnapshot(snapshot(rows));let id=m.statusFlight!.id
|
|
43
|
-
check(!m.expanded && !m.showingExpanded,"automatic panel must preserve travelling stroke")
|
|
44
|
-
if cancel {rows[0].approval=nil;m.applySnapshot(snapshot(rows))}
|
|
45
|
-
m.finishStatusFlight(id:id)
|
|
46
|
-
check(m.expanded == !cancel,"only current approval may open after ink arrives")
|
|
47
|
-
check(m.needsAction == !cancel,"cancelled question cannot reappear")
|
|
48
|
-
}
|
|
49
|
-
// A result queued before an approval must retain its own identity and order.
|
|
50
|
-
let queued=BoardModel();queued.previewMode=true;var rows=busyRows(2)
|
|
51
|
-
queued.applySnapshot(snapshot(rows));queued.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
52
|
-
rows[0].busy=false;rows[0].unread=true
|
|
53
|
-
queued.applySnapshot(snapshot(rows));let first=queued.statusFlight!.id
|
|
54
|
-
rows[1].approval=NotchApproval(id:"queued-approval",toolName:"offline")
|
|
55
|
-
queued.applySnapshot(snapshot(rows));check(queued.statusFlight?.id==first,"decision does not replace moving success stroke")
|
|
56
|
-
queued.finishStatusFlight(id:first);let second=queued.statusFlight!.id
|
|
57
|
-
check(queued.statusFlight?.decision == true,"decision uses next brush slot")
|
|
58
|
-
queued.finishStatusFlight(id:first);check(queued.statusFlight?.id==second,"old callback cannot clear current stroke")
|
|
59
|
-
queued.finishStatusFlight(id:second)
|
|
60
|
-
check(queued.completedUnreadCount==1 && queued.needsAction,"green result survives yellow arrival")
|
|
61
|
-
// A decision reply resumes the same task; never increment the visible blue
|
|
62
|
-
// count before the yellow lamp has returned to it.
|
|
63
|
-
for count in [1,2] {
|
|
64
|
-
let m=BoardModel();m.previewMode=true;var rows=busyRows(count)
|
|
65
|
-
m.applySnapshot(snapshot(rows));m.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
66
|
-
rows[0].approval=NotchApproval(id:"roundtrip",toolName:"offline")
|
|
67
|
-
m.applySnapshot(snapshot(rows));m.finishStatusFlight(id:m.statusFlight!.id)
|
|
68
|
-
m.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
69
|
-
check(m.busyCount==count-1 && m.needsAction,"awaiting preserves remaining task count")
|
|
70
|
-
rows[0].approval=nil;m.applySnapshot(snapshot(rows))
|
|
71
|
-
if count==2 {check(m.retainedBusyCount==1,"yellow reply must not show blue 2 before merging")}
|
|
72
|
-
guard let reply=m.decisionReturn else {check(false,"reply must own a continuous return timeline");continue}
|
|
73
|
-
check(reply.travelling == (count==2),"reply draws locally or travels to remaining blue")
|
|
74
|
-
m.applySnapshot(snapshot(rows));check(m.decisionReturn?.id==reply.id,"polling does not restart reply")
|
|
75
|
-
m.tickOrbitLayout(at:reply.startedAt.addingTimeInterval(reply.duration*0.1))
|
|
76
|
-
if count==2 {check(m.orbitLayout.decision==1 && m.retainedBusyCount==1,"yellow clears before shell closes and count merges")}
|
|
77
|
-
let finalAngle=m.decisionAngle(at:reply.startedAt.addingTimeInterval(reply.duration))
|
|
78
|
-
m.finishDecisionReturn(id:reply.id)
|
|
79
|
-
check(m.decisionReturn==nil,"reply releases presentation state")
|
|
80
|
-
check(abs(m.decisionAngle(at:reply.startedAt.addingTimeInterval(reply.duration))-finalAngle)<0.00001,"spin phase survives reply handoff")
|
|
81
|
-
m.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
82
|
-
check(m.busyCount==count && !m.needsAction,"reply returns original task count")
|
|
83
|
-
check(m.orbitLayout==OrbitLayout(middle:1),"reply leaves one blue lamp without yellow slot")
|
|
84
|
-
}
|
|
85
|
-
for count in [1,2] {
|
|
86
|
-
let m=BoardModel();m.previewMode=true;var rows=busyRows(count)
|
|
87
|
-
m.applySnapshot(snapshot(rows));m.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
88
|
-
rows[0].approval=NotchApproval(id:"fast",toolName:"offline")
|
|
89
|
-
m.applySnapshot(snapshot(rows));let outward=m.statusFlight!.id
|
|
90
|
-
rows[0].approval=nil;m.applySnapshot(snapshot(rows))
|
|
91
|
-
check(m.statusFlight?.id==outward && m.decisionReturn==nil,"fast reply finishes its outgoing ink first")
|
|
92
|
-
check(m.orbitBusyCount==count-1,"fast reply cannot prematurely change outgoing blue count")
|
|
93
|
-
m.finishStatusFlight(id:outward)
|
|
94
|
-
guard let reply=m.decisionReturn else {check(false,"fast reply starts return after arrival");continue}
|
|
95
|
-
m.finishStatusFlight(id:outward);check(m.decisionReturn?.id==reply.id,"old outbound callback cannot erase reply")
|
|
96
|
-
m.finishDecisionReturn(id:reply.id)
|
|
97
|
-
check(m.orbitLayout==OrbitLayout(middle:1) && m.busyCount==count,"fast reply ends on original blue count")
|
|
98
|
-
}
|
|
99
|
-
// A folded panel must reveal the beginning of the return, not its last frame.
|
|
100
|
-
let panel=BoardModel();var panelRows=busyRows(2)
|
|
101
|
-
panel.applySnapshot(snapshot(panelRows));panel.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
102
|
-
panelRows[0].approval=NotchApproval(id:"panel",toolName:"offline")
|
|
103
|
-
panel.applySnapshot(snapshot(panelRows));panel.finishStatusFlight(id:panel.statusFlight!.id)
|
|
104
|
-
check(panel.expanded && panel.showingExpanded,"approval expanded after arrival")
|
|
105
|
-
panelRows[0].approval=nil;let replyTime=Date();panel.applySnapshot(snapshot(panelRows))
|
|
106
|
-
check(!panel.expanded && panel.showingExpanded,"reply folds panel")
|
|
107
|
-
check((panel.decisionReturn?.startedAt.timeIntervalSince(replyTime) ?? 0)>=NotchGeometryAnimation.duration-0.02,"return waits for folded lamp surface")
|
|
108
|
-
// Cancelling a question without resuming that row must not increment unrelated work.
|
|
109
|
-
let cancel=BoardModel();cancel.previewMode=true;var cancelRows=busyRows(2)
|
|
110
|
-
cancelRows[0].approval=NotchApproval(id:"cancel",toolName:"offline")
|
|
111
|
-
cancel.applySnapshot(snapshot(cancelRows));cancel.tickOrbitLayout(at:Date().addingTimeInterval(2))
|
|
112
|
-
cancelRows[0].approval=nil;cancelRows[0].busy=false;cancel.applySnapshot(snapshot(cancelRows))
|
|
113
|
-
check(cancel.decisionReturn==nil && cancel.busyCount==1,"cancel is not a task resume")
|
|
114
|
-
for angle in stride(from:0.0,to:2 * Double.pi,by:Double.pi/4) {
|
|
115
|
-
let route=OrbitBrushRoute(failed:false,angle:angle)
|
|
116
|
-
var previous=DecisionReturnFrame(progress:0,angle:angle)
|
|
117
|
-
check(previous.fill==1 && previous.countMix==0,"return starts as existing yellow and existing count")
|
|
118
|
-
for i in 1...240 {
|
|
119
|
-
let frame=DecisionReturnFrame(progress:Double(i)/240,angle:angle)
|
|
120
|
-
check(frame.distance>=previous.distance && frame.distance-previous.distance<3,"return brush advances continuously")
|
|
121
|
-
check(frame.tint<=previous.tint+0.00001 && abs(frame.tint-previous.tint)<0.08,"return colour blends into blue")
|
|
122
|
-
check(frame.collapse==0 || frame.fill==0,"yellow solid clears before lamps converge")
|
|
123
|
-
check(frame.countMix==0 || frame.distance>=route.returned,"count changes only after ink enters blue ring")
|
|
124
|
-
previous=frame
|
|
125
|
-
}
|
|
126
|
-
check(previous.fill==0 && previous.tint==0 && previous.countMix==1,"return settles on blue new count")
|
|
127
|
-
let near=DecisionReturnFrame(progress:0.9999,angle:angle)
|
|
128
|
-
let velocity=(previous.distance-near.distance)/(StatusFlight.duration*0.0001)
|
|
129
|
-
check(abs(velocity-9.5*DecisionSpin.runningVelocity)<0.05,"pen speed matches final rotating ring")
|
|
130
|
-
}
|
|
131
|
-
print("FAILURES=\(failures)");exit(failures==0 ? 0:1)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import AppKit
|
|
2
|
-
import SwiftUI
|
|
3
|
-
import Combine
|
|
4
|
-
|
|
5
|
-
@main enum ScrollbarProbe {
|
|
6
|
-
@MainActor static func main() {
|
|
7
|
-
let app = NSApplication.shared
|
|
8
|
-
app.setActivationPolicy(.accessory)
|
|
9
|
-
let model = BoardModel()
|
|
10
|
-
model.previewMode = true
|
|
11
|
-
model.maximumExpandedHeight = 800
|
|
12
|
-
let panel = NotchPanel(size:NSSize(width:38,height:44))
|
|
13
|
-
let host = NotchHostingView(rootView:RootView(model:model,panelSize:CGSize(width:480,height:800),restSize:CGSize(width:38,height:44)))
|
|
14
|
-
host.sizingOptions = []
|
|
15
|
-
panel.embedHost(host)
|
|
16
|
-
panel.setFrame(NSRect(x:-2000,y:-2000,width:38,height:44),display:true)
|
|
17
|
-
panel.orderFrontRegardless()
|
|
18
|
-
var subscriptions = Set<AnyCancellable>()
|
|
19
|
-
Publishers.CombineLatest(model.$currentIslandWidth,model.$currentIslandHeight)
|
|
20
|
-
.receive(on:DispatchQueue.main).sink {w,h in panel.resizeAnchored(to:NSSize(width:w,height:h))}
|
|
21
|
-
.store(in:&subscriptions)
|
|
22
|
-
@MainActor func scrollViews(_ view:NSView)->[NSScrollView] {
|
|
23
|
-
((view as? NSScrollView).map {[$0]} ?? []) + view.subviews.flatMap(scrollViews)
|
|
24
|
-
}
|
|
25
|
-
Task { @MainActor in
|
|
26
|
-
let rows=(0..<5).map {NotchRow(id:"fixture-\($0)",title:"Running task \($0+1)",child:false,busy:true,unread:false)}
|
|
27
|
-
model.applySnapshot(NotchSnapshot(ok:true,generatedAt:0,origin:"offline",rows:rows))
|
|
28
|
-
try? await Task.sleep(for:.milliseconds(800))
|
|
29
|
-
var failures=0, samples=0, flashFrames=0
|
|
30
|
-
@MainActor func check(_ condition:Bool,_ message:String) {
|
|
31
|
-
if !condition {failures += 1;print("FAIL \(message)")}
|
|
32
|
-
}
|
|
33
|
-
@MainActor func inspectShortExpansion(_ scenario:String) async {
|
|
34
|
-
model.expanded=true
|
|
35
|
-
for frame in 0..<45 {
|
|
36
|
-
try? await Task.sleep(for:.milliseconds(16))
|
|
37
|
-
host.layoutSubtreeIfNeeded()
|
|
38
|
-
let scrolls=scrollViews(host)
|
|
39
|
-
for (i,scroll) in scrolls.enumerated() {
|
|
40
|
-
guard let bar=scroll.verticalScroller else {continue}
|
|
41
|
-
let visible=scroll.hasVerticalScroller && !bar.isHiddenOrHasHiddenAncestor && bar.alphaValue>0 && bar.bounds.height>0 && bar.knobProportion<0.999
|
|
42
|
-
if visible {failures += 1;flashFrames += 1}
|
|
43
|
-
if frame<4 || visible || frame==44 {
|
|
44
|
-
print("SCENARIO=\(scenario) FRAME=\(frame) SCROLL=\(i) PANEL=\(Int(panel.frame.height)) DOCUMENT=\(Int(scroll.documentView?.frame.height ?? 0)) VIEWPORT=\(Int(scroll.contentView.bounds.height)) HAS_BAR=\(scroll.hasVerticalScroller) HIDDEN=\(bar.isHiddenOrHasHiddenAncestor) ALPHA=\(bar.alphaValue) KNOB=\(bar.knobProportion) VISIBLE=\(visible)")
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
samples += scrolls.count
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
await inspectShortExpansion("five-tasks")
|
|
51
|
-
model.expanded=false
|
|
52
|
-
try? await Task.sleep(for:.milliseconds(600))
|
|
53
|
-
await inspectShortExpansion("reopen")
|
|
54
|
-
model.expanded=false
|
|
55
|
-
try? await Task.sleep(for:.milliseconds(70))
|
|
56
|
-
await inspectShortExpansion("reverse-collapse")
|
|
57
|
-
check(samples>0,"no native scroll views reached")
|
|
58
|
-
|
|
59
|
-
// A real overflow must keep both its indicator and a movable viewport.
|
|
60
|
-
model.maximumExpandedHeight=400
|
|
61
|
-
let question=NotchQuestion(id:"long",question:"Long question",options:[NotchOption(label:"Option",description:String(repeating:"Long offline content. ",count:150))])
|
|
62
|
-
let row=NotchRow(id:"question",title:"Local fixture",child:false,busy:true,unread:false,ask:NotchAsk(id:"ask",questions:[question]))
|
|
63
|
-
model.applySnapshot(NotchSnapshot(ok:true,generatedAt:0,origin:"offline",rows:[row]))
|
|
64
|
-
try? await Task.sleep(for:.milliseconds(850))
|
|
65
|
-
host.layoutSubtreeIfNeeded()
|
|
66
|
-
check(abs(panel.frame.height-400)<2,"long content reaches the screen cap")
|
|
67
|
-
if let scroll=scrollViews(host).first,let document=scroll.documentView {
|
|
68
|
-
let overflow=document.frame.height-scroll.contentView.bounds.height
|
|
69
|
-
check(overflow>20,"long fixture actually overflows")
|
|
70
|
-
check(scroll.hasVerticalScroller,"real overflow retains its indicator")
|
|
71
|
-
let before=scroll.contentView.bounds.origin.y
|
|
72
|
-
scroll.contentView.scroll(to:NSPoint(x:0,y:overflow))
|
|
73
|
-
scroll.reflectScrolledClipView(scroll.contentView)
|
|
74
|
-
check(scroll.contentView.bounds.origin.y>before+20,"long content remains scrollable")
|
|
75
|
-
print("LONG_OVERFLOW=\(Int(overflow)) SCROLLED=\(Int(scroll.contentView.bounds.origin.y)) INDICATOR=\(scroll.hasVerticalScroller)")
|
|
76
|
-
} else {check(false,"long fixture has a native scroll view")}
|
|
77
|
-
model.maximumExpandedHeight=800
|
|
78
|
-
model.applySnapshot(NotchSnapshot(ok:true,generatedAt:0,origin:"offline",rows:rows))
|
|
79
|
-
try? await Task.sleep(for:.milliseconds(850))
|
|
80
|
-
check(panel.frame.height<400,"short content shrinks after long content")
|
|
81
|
-
model.expanded=false
|
|
82
|
-
try? await Task.sleep(for:.milliseconds(600))
|
|
83
|
-
await inspectShortExpansion("after-long-content")
|
|
84
|
-
print("SCROLLBAR_FLASH_FRAMES=\(flashFrames)")
|
|
85
|
-
print("FAILURES=\(failures)")
|
|
86
|
-
exit(failures==0 ? 0:1)
|
|
87
|
-
}
|
|
88
|
-
app.run()
|
|
89
|
-
withExtendedLifetime(subscriptions) {}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
base=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
4
|
-
work="$base/.build/expanded-height-probe"
|
|
5
|
-
mkdir -p "$work/Sources"
|
|
6
|
-
cp -R "$base/Sources/Resources" "$work/Sources/"
|
|
7
|
-
cp "$base/Package.swift" "$work/Package.swift"
|
|
8
|
-
for file in IdleRobot.swift NotchMarkdown.swift RootView.swift Panel.swift Client.swift StatusOrbit.swift; do
|
|
9
|
-
cp "$base/Sources/$file" "$work/Sources/$file"
|
|
10
|
-
done
|
|
11
|
-
cp "$base/Tests/ExpandedHeightProbe.swift" "$work/Sources/main.swift"
|
|
12
|
-
swift build --package-path "$work" -c release
|
|
13
|
-
"$work/.build/release/dsh-notch"
|
package/macos/Tests/geometry.sh
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
base=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
4
|
-
work=$(mktemp -d "${TMPDIR:-/tmp}/notch-geometry.XXXXXX")
|
|
5
|
-
trap 'rm -rf "$work"' EXIT
|
|
6
|
-
mkdir "$work/Sources"
|
|
7
|
-
cp -R "$base/Sources/Resources" "$work/Sources/Resources"
|
|
8
|
-
cp "$base/Sources/IdleRobot.swift" "$work/Sources/"
|
|
9
|
-
cp "$base/Sources/NotchMarkdown.swift" "$work/Sources/"
|
|
10
|
-
cp "$base/Package.swift" "$work/Package.swift"
|
|
11
|
-
cp "$base/Sources/RootView.swift" "$base/Sources/Panel.swift" "$base/Sources/Client.swift" "$base/Sources/StatusOrbit.swift" "$work/Sources/"
|
|
12
|
-
cp "$base/Tests/GeometryProbe.swift" "$work/Sources/main.swift"
|
|
13
|
-
swift build --package-path "$work" -c release
|
|
14
|
-
"$work/.build/release/dsh-notch"
|
package/macos/Tests/idle.sh
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
base=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
4
|
-
work=$(mktemp -d "${TMPDIR:-/tmp}/notch-idle.XXXXXX")
|
|
5
|
-
trap 'rm -rf "$work"' EXIT
|
|
6
|
-
mkdir "$work/Sources"
|
|
7
|
-
cp -R "$base/Sources/Resources" "$work/Sources/Resources"
|
|
8
|
-
cp "$base/Sources/IdleRobot.swift" "$work/Sources/"
|
|
9
|
-
cp "$base/Sources/NotchMarkdown.swift" "$work/Sources/"
|
|
10
|
-
cp "$base/Package.swift" "$work/Package.swift"
|
|
11
|
-
cp "$base/Sources/RootView.swift" "$base/Sources/Panel.swift" "$base/Sources/Client.swift" "$base/Sources/StatusOrbit.swift" "$work/Sources/"
|
|
12
|
-
cp "$base/Tests/IdleProbe.swift" "$work/Sources/main.swift"
|
|
13
|
-
swift build --package-path "$work" -c release
|
|
14
|
-
"$work/.build/release/dsh-notch"
|
package/macos/Tests/motion.sh
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
base=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
4
|
-
work=$(mktemp -d "${TMPDIR:-/tmp}/notch-motion.XXXXXX")
|
|
5
|
-
trap 'rm -rf "$work"' EXIT
|
|
6
|
-
mkdir "$work/Sources"
|
|
7
|
-
cp -R "$base/Sources/Resources" "$work/Sources/Resources"
|
|
8
|
-
cp "$base/Sources/IdleRobot.swift" "$work/Sources/"
|
|
9
|
-
cp "$base/Sources/NotchMarkdown.swift" "$work/Sources/"
|
|
10
|
-
cp "$base/Package.swift" "$work/Package.swift"
|
|
11
|
-
cp "$base/Sources/RootView.swift" "$base/Sources/Panel.swift" "$base/Sources/Client.swift" "$base/Sources/StatusOrbit.swift" "$work/Sources/"
|
|
12
|
-
cp "$base/Tests/MotionProbe.swift" "$work/Sources/main.swift"
|
|
13
|
-
swift build --package-path "$work" -c release
|
|
14
|
-
"$work/.build/release/dsh-notch"
|
package/macos/Tests/outcome.sh
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
base=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
4
|
-
work=$(mktemp -d "${TMPDIR:-/tmp}/notch-outcome.XXXXXX")
|
|
5
|
-
trap 'rm -rf "$work"' EXIT
|
|
6
|
-
mkdir "$work/Sources"
|
|
7
|
-
cp -R "$base/Sources/Resources" "$work/Sources/Resources"
|
|
8
|
-
cp "$base/Sources/IdleRobot.swift" "$work/Sources/"
|
|
9
|
-
cp "$base/Sources/NotchMarkdown.swift" "$work/Sources/"
|
|
10
|
-
cp "$base/Package.swift" "$work/Package.swift"
|
|
11
|
-
cp "$base/Sources/RootView.swift" "$base/Sources/Panel.swift" "$base/Sources/Client.swift" "$base/Sources/StatusOrbit.swift" "$work/Sources/"
|
|
12
|
-
cp "$base/Tests/OutcomeProbe.swift" "$work/Sources/main.swift"
|
|
13
|
-
swift build --package-path "$work" -c release
|
|
14
|
-
"$work/.build/release/dsh-notch"
|
package/macos/Tests/scrollbar.sh
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
base=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
4
|
-
work="$base/.build/scrollbar-probe"
|
|
5
|
-
mkdir -p "$work/Sources"
|
|
6
|
-
cp -R "$base/Sources/Resources" "$work/Sources/"
|
|
7
|
-
cp "$base/Package.swift" "$work/Package.swift"
|
|
8
|
-
for file in IdleRobot.swift NotchMarkdown.swift RootView.swift Panel.swift Client.swift StatusOrbit.swift; do
|
|
9
|
-
cp "$base/Sources/$file" "$work/Sources/$file"
|
|
10
|
-
done
|
|
11
|
-
cp "$base/Tests/ScrollbarProbe.swift" "$work/Sources/main.swift"
|
|
12
|
-
swift build --package-path "$work" -c release
|
|
13
|
-
"$work/.build/release/dsh-notch"
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test';import assert from 'node:assert/strict';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import {mkdtempSync, rmSync} from 'node:fs';
|
|
4
|
-
import {syncBuiltinESMExports} from 'node:module';
|
|
5
|
-
import {after} from 'node:test';
|
|
6
|
-
const testDir=mkdtempSync(os.tmpdir()+'/notch-answer-test-');
|
|
7
|
-
const realHomedir=os.homedir;
|
|
8
|
-
os.homedir=()=>testDir; syncBuiltinESMExports();
|
|
9
|
-
const {Board}=await import('../src/board.ts');
|
|
10
|
-
os.homedir=realHomedir; syncBuiltinESMExports();
|
|
11
|
-
after(()=>rmSync(testDir,{recursive:true,force:true}));
|
|
12
|
-
const session={id:'notch-lifecycle-test',header:{},snapshotEvents:()=>[]};
|
|
13
|
-
const ctx={sessions:{list:()=>[session]},agents:{get:()=>({status:'running'})},get:()=>undefined,logger:{warn(){}}};
|
|
14
|
-
test('Notch answer cancels the web wait and preserves caller signal',async()=>{
|
|
15
|
-
const board=new Board(ctx), owner=new AbortController();const request={agent:{id:session.id},questions:[{id:'q',question:'test'}],signal:owner.signal};let webVisible=true;
|
|
16
|
-
const pending=board.holdAsk(request,()=>new Promise((resolve,reject)=>{request.signal.addEventListener('abort',()=>{webVisible=false;reject(Error('web wait ended'))},{once:true})}));
|
|
17
|
-
const ask=board.snapshot('http://localhost').rows[0].ask;assert.ok(ask);assert.equal(board.answerAsk(ask.id,[{id:'q',selected:['test']}]),true);
|
|
18
|
-
assert.deepEqual(await pending,{answers:[{id:'q',selected:['test']}]});assert.equal(webVisible,false);assert.equal(owner.signal.aborted,false);assert.equal(request.signal,owner.signal);
|
|
19
|
-
});
|
|
20
|
-
test('Web answer removes the Notch request',async()=>{
|
|
21
|
-
const board=new Board(ctx);let finish;const answer={answers:[{id:'q',selected:[]}]};
|
|
22
|
-
const pending=board.holdAsk({agent:{id:session.id},questions:[{id:'q',question:'test'}]},()=>new Promise(r=>finish=r));await Promise.resolve();finish(answer);assert.equal(await pending,answer);assert.equal(board.snapshot('http://localhost').rows[0].ask,undefined);
|
|
23
|
-
});
|
package/tests/board.test.mjs
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import {Board} from '../src/board.ts'
|
|
4
|
-
const session={id:'session-regression',header:{},snapshotEvents:()=>[]}
|
|
5
|
-
const ctx={sessions:{list:()=>[session]},agents:{get:()=>({status:'running'})},get:()=>undefined,logger:{warn:()=>{}}}
|
|
6
|
-
test('snapshot and background notification handle live sessions without missing seen state',()=>{const board=new Board(ctx);let notifications=0;board.onChange(()=>{assert.equal(board.snapshot('http://localhost').rows.length,1);notifications++});board.notify();assert.equal(notifications,1)})
|
|
7
|
-
test('one broken subscriber cannot crash Host or stop other subscribers',()=>{const board=new Board(ctx);let n=0;board.onChange(()=>{throw Error('broken stream')});board.onChange(()=>n++);assert.doesNotThrow(()=>board.notify());assert.equal(n,1)})
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import {installBrowseSync} from '../src/browse-sync.ts'
|
|
4
|
-
class Controller { history={page:async()=>({records:[1]}),follow:async function*(){yield {records:[1]}}}; page(request,signal){return this.history.page(request,signal)} follow(request,signal){return this.history.follow(request,signal)} }
|
|
5
|
-
const req={address:{sessionId:'test'}}
|
|
6
|
-
test('legacy synchronous Notch wrapper reproduces exact error; repair removes all nested wrappers',async()=>{
|
|
7
|
-
const c=new Controller();const board={markSeen(){}};const getTargetSessionId=a=>a.sessionId
|
|
8
|
-
for(let i=0;i<2;i++){const origFollow=c.follow.bind(c);c.follow=function* (req, signal) {const sid=getTargetSessionId(req.address);if(sid)board.markSeen(sid);return yield* origFollow(req,signal)}}
|
|
9
|
-
await assert.rejects(async()=>{for await(const x of c.follow(req)){}},/is not iterable/)
|
|
10
|
-
const stop=installBrowseSync(c,()=>{});const rows=[];for await(const x of c.follow(req))rows.push(x)
|
|
11
|
-
assert.deepEqual(rows,[{records:[1]}]);stop();assert.equal(Object.hasOwn(c,'follow'),false)
|
|
12
|
-
})
|
|
13
|
-
test('preserves async stream identity, return, cancellation; repeated generations dispose cleanly',async()=>{
|
|
14
|
-
const c=new Controller();let returned=0;const signal=AbortSignal.abort();const source={async *[Symbol.asyncIterator](){try{yield 1;yield 2}finally{returned++}}}
|
|
15
|
-
c.history.follow=(r,s)=>{assert.equal(s,signal);return source}
|
|
16
|
-
let a=0,b=0;const stopA=installBrowseSync(c,()=>a++);const stopB=installBrowseSync(c,()=>b++)
|
|
17
|
-
assert.equal(c.follow(req,signal),source);stopA();const stream=c.follow(req,signal);for await(const v of stream){break}
|
|
18
|
-
assert.equal(a,1);assert.equal(b,2);assert.equal(returned,1);stopB();stopB();assert.equal(Object.hasOwn(c,'follow'),false)
|
|
19
|
-
const stop=installBrowseSync(c,()=>{throw Error('read marker failed')});assert.deepEqual(await c.page(req),{records:[1]});stop()
|
|
20
|
-
})
|
package/tests/mirror.test.mjs
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import {test} from 'node:test';import assert from 'node:assert/strict';import {Board} from '../src/board.ts';
|
|
2
|
-
const ctx={sessions:{list:()=>[]},agents:{get:()=>undefined},get:()=>undefined,logger:{warn:()=>{}}};
|
|
3
|
-
test('sidebar completion survives cold session; opening in DSH clears green; background page cannot overwrite',()=>{const b=new Board(ctx);const row={id:'session-cold',title:'Cold',running:false,completed:true};assert.equal(b.syncSidebar({clientId:'app',focused:true,rows:[row]}),true);assert.equal(b.snapshot('').rows[0].unread,true);assert.equal(b.requestFocus(row.id),true);b.syncSidebar({clientId:'app',focused:true,rows:[]});assert.equal(b.snapshot('').rows.length,0);b.syncSidebar({clientId:'browser',focused:false,rows:[row]});assert.equal(b.snapshot('').rows.length,0)});
|
|
4
|
-
test('malformed mirror never replaces previous valid state',()=>{const b=new Board(ctx);assert.equal(b.syncSidebar({clientId:'app',rows:[{id:123}]}),false);assert.deepEqual(b.snapshot('').rows,[])});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { readFileSync } from 'node:fs'
|
|
4
|
-
|
|
5
|
-
const RANGE = '>=0.1.7-rc.1 <0.1.8'
|
|
6
|
-
const PEERS = [
|
|
7
|
-
'@deepseek-ai/dsh',
|
|
8
|
-
'@deepseek-ai/dsh-host-webserver',
|
|
9
|
-
'@deepseek-ai/dsh-session',
|
|
10
|
-
'@deepseek-ai/dsh-user-approval',
|
|
11
|
-
'@deepseek-ai/dsh-user-questions',
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
test('Harness peer range is the 0.1.7-rc.1 desk pin', () => {
|
|
15
|
-
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
16
|
-
assert.equal(pkg.version, '0.3.1')
|
|
17
|
-
for (const name of PEERS) {
|
|
18
|
-
assert.equal(pkg.peerDependencies[name], RANGE)
|
|
19
|
-
assert.equal(pkg.peerDependenciesMeta[name].optional, true)
|
|
20
|
-
}
|
|
21
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { foldSession } from '../src/session-state.ts'
|
|
4
|
-
|
|
5
|
-
function session(events) {
|
|
6
|
-
return { snapshotEvents: () => events }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
test('aborted last turn is not a failure lamp', () => {
|
|
10
|
-
const folded = foldSession(session([
|
|
11
|
-
{ type: 'turn/start', time: 1 },
|
|
12
|
-
{ type: 'turn/end', time: 2, data: { reason: { kind: 'aborted' } } },
|
|
13
|
-
]))
|
|
14
|
-
assert.equal(folded.busy, false)
|
|
15
|
-
assert.equal(folded.lastTurn.kind, 'aborted')
|
|
16
|
-
assert.equal(folded.lastTurn.failed, false)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test('interrupted last turn is not a failure lamp', () => {
|
|
20
|
-
const folded = foldSession(session([
|
|
21
|
-
{ type: 'turn/start', time: 1 },
|
|
22
|
-
{ type: 'turn/end', time: 2, data: { reason: { kind: 'interrupted' } } },
|
|
23
|
-
]))
|
|
24
|
-
assert.equal(folded.lastTurn.failed, false)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('a new turn clears the previous abort so a running session cannot stay red', () => {
|
|
28
|
-
const folded = foldSession(session([
|
|
29
|
-
{ type: 'turn/start', time: 1 },
|
|
30
|
-
{ type: 'turn/end', time: 2, data: { reason: { kind: 'aborted' } } },
|
|
31
|
-
{ type: 'turn/start', time: 3 },
|
|
32
|
-
]))
|
|
33
|
-
assert.equal(folded.busy, true)
|
|
34
|
-
assert.equal(folded.lastTurn, undefined)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('a fork-seed closer is not a failure lamp', () => {
|
|
38
|
-
const folded = foldSession(session([
|
|
39
|
-
{ type: 'turn/start', time: 1 },
|
|
40
|
-
{ type: 'turn/end', time: 2, data: { reason: { kind: 'forked' } } },
|
|
41
|
-
]))
|
|
42
|
-
assert.equal(folded.busy, false)
|
|
43
|
-
assert.equal(folded.lastTurn.kind, 'forked')
|
|
44
|
-
assert.equal(folded.lastTurn.failed, false)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('error still counts as failed after the turn ends', () => {
|
|
48
|
-
const folded = foldSession(session([
|
|
49
|
-
{ type: 'turn/start', time: 1 },
|
|
50
|
-
{ type: 'turn/end', time: 2, data: { reason: { kind: 'error' } } },
|
|
51
|
-
]))
|
|
52
|
-
assert.equal(folded.busy, false)
|
|
53
|
-
assert.equal(folded.lastTurn.failed, true)
|
|
54
|
-
})
|