2026-10-17 09:33:27 -07:00
import ByreAPI
2026-10-17 07:26:18 -07:00
import SwiftUI
typealias FindingState = Components . Schemas . FindingState
private let findingHead = Re (# "^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@" #)
private func stateText ( _ r : Review ) -> String {
switch r . state {
case . running : "reviewing on \( r . vendor ) ( \( r . model ) )…"
case . done : reviewCounts ( r )
case . failed : "failed"
case . skipped : "skipped: no changes"
}
}
@ MainActor
2026-10-17 07:43:33 -07:00
private enum SnapshotPatches {
2026-10-17 07:26:18 -07:00
static var changes : [ String : Task < TaskChanges , any Error >] = [:]
static var patches : [ String : Task < String ?, any Error >] = [:]
static func patch ( _ api : API , _ taskId : String , _ n : Int , _ path : String ) async throws -> String ? {
2026-10-18 20:58:56 -07:00
let key = " \( api . origin . absoluteString ) / \( taskId ) / \( n ) / \( path ) "
2026-10-17 07:26:18 -07:00
if let cached = patches [ key ] {
return try await cached . value
}
let fetched = Task < String ?, any Error > {
2026-10-18 20:58:56 -07:00
let ck = " \( api . origin . absoluteString ) / \( taskId ) / \( n ) "
2026-10-17 07:26:18 -07:00
let pending = changes [ ck ] ?? Task {
2026-10-17 07:43:33 -07:00
try await api . client . getTaskChanges ( path : . init ( taskId : taskId ), query : . init ( snapshot : n )). ok . body . json
2026-10-17 07:26:18 -07:00
}
changes [ ck ] = pending
let c : TaskChanges
do {
c = try await pending . value
} catch {
changes [ ck ] = nil
throw error
}
guard let f = c . files . first ( where : { $0 . path == path }), ! f . binary else {
return nil
}
return try await api . client . getTaskDiff ( path : . init ( taskId : taskId ), query : . init ( base : c . base , tree : c . tree , path : f . path , oldPath : f . oldPath )). ok . body . json . patch
}
patches [ key ] = fetched
do {
return try await fetched . value
} catch {
patches [ key ] = nil
throw error
}
}
}
private func linesAround ( _ patch : String , _ line : Int , _ end : Int ) -> [( kind : PatchLine , text : String , at : Int )] {
var lines : [( kind : PatchLine , text : String , at : Int )] = []
var n = 0
var inHunk = false
for l in patch . components ( separatedBy : " \n " ) {
if let m = findingHead . exec ( l ) {
n = Int ( m [ 1 ] ! ) ?? 0
inHunk = true
continue
}
if ! inHunk {
continue
}
if l . hasPrefix ( "+" ) {
lines . append ((. add , l , n ))
n += 1
} else if l . hasPrefix ( "-" ) {
lines . append ((. del , l , n ))
} else if l . hasPrefix ( " " ) {
lines . append ((. ctx , l , n ))
n += 1
}
}
return lines . filter { $0 . at >= line - 3 && $0 . at <= end + 3 }
}
struct ReviewsView : View {
@ Environment ( Store . self ) private var store
@ ViewState private var reviews : [ Review ]?
@ ViewState private var error = ""
@ ViewState private var target = 0
var body : some View {
VStack ( spacing : 0 ) {
content
}
. frame ( maxHeight : . infinity , alignment : . top )
. task ( id : store . selectedTaskId ) {
target += 1
reviews = nil
error = ""
await load ()
let off = store . selectedFeed ?. appended . add { events in
if events . contains ( where : isReviewEvent ) {
Task {
await load ()
}
}
}
await untilCancelled ()
off ?()
target += 1
}
}
private func load () async {
guard let taskId = store . selectedTaskId else {
return
}
target += 1
let started = target
do {
let fresh = try await store . api . client . listReviews ( path : . init ( taskId : taskId )). ok . body . json
if started == target {
reviews = fresh
error = ""
}
} catch {
if started == target {
reviews = nil
self . error = errorText ( error )
}
}
}
@ ViewBuilder
private var content : some View {
if let taskId = store . selectedTaskId {
if ! error . isEmpty {
EmptyText ( text : error , error : true )
} else if let reviews {
if let lastReview = reviews . last {
let n = store . reviewed ?? lastReview . snapshotN
let shown = reviews . last { $0 . snapshotN == n }
let ns = Array ( Set ([ n ] + reviews . map ( \ . snapshotN ))). sorted ( by : > )
let next = ns . filter { $0 > n }. last
let prev = ns . first { $0 < n }
HStack ( spacing : 8 ) {
Picker ( "" , selection : Binding ( get : { n }, set : { store . pickReview ( $0 ) })) {
ForEach ( ns , id : \ . self ) { s in
Text ( "snapshot # \( s ) " ). tag ( s )
}
}
. labelsHidden ()
. pickerStyle (. menu )
. fixedSize ()
. help ( "Snapshot whose review to show" )
step ( prev ?? n - 1 , "← # \( prev ?? n - 1 ) " , ns )
step ( next ?? n + 1 , "# \( next ?? n + 1 ) →" , ns )
}
. modifier ( HeaderBar ())
ScrollView {
VStack ( alignment : . leading , spacing : 12 ) {
if let shown {
ReviewCard ( review : shown , taskId : taskId )
2026-10-17 10:54:06 -07:00
. id ( shown . id )
2026-10-17 07:26:18 -07:00
} else {
Muted ( text : "No review of snapshot # \( n ) yet." )
}
}
. padding (. horizontal , 16 )
. padding (. top , 8 )
. padding (. bottom , 16 )
}
} else {
EmptyText ( text : "No reviews yet. Each new snapshot of the task is reviewed by an agent from the other vendor." )
}
} else {
EmptyText ( text : "Loading…" )
}
} else {
EmptyText ( text : "No task selected." )
}
}
private func step ( _ to : Int , _ label : String , _ ns : [ Int ]) -> some View {
Button ( label ) {
store . pickReview ( to )
}
. buttonStyle ( SmallLinkButtonStyle ( color : Palette . text ))
. disabled ( ! ns . contains ( to ))
}
}
struct ReviewCard : View {
let review : Review
let taskId : String ?
var body : some View {
VStack ( alignment : . leading , spacing : 6 ) {
HStack ( alignment : . firstTextBaseline , spacing : 10 ) {
Text ( "snapshot # \( review . snapshotN ) " )
. font ( Fonts . font ( 11 , weight : 700 ))
. foregroundStyle ( Palette . text )
Text ( stateText ( review ))
. foregroundStyle ( review . state == . failed ? Palette . warn : Palette . muted )
Spacer ( minLength : 0 )
Text ( ago ( review . createdAt ))
. help ( isoText ( review . createdAt ))
2026-10-17 10:54:06 -07:00
if review . state == . running {
StopReviewButton ( reviewId : review . id )
}
2026-10-17 07:26:18 -07:00
}
. font ( Fonts . font ( 11 ))
. foregroundStyle ( Palette . muted )
if review . state == . failed {
Text ( review . error )
. foregroundStyle ( Palette . danger )
. textSelection (. enabled )
}
if review . state == . done {
ReviewResult ( review : review , taskId : taskId )
}
}
. padding (. horizontal , 14 )
. padding (. vertical , 10 )
. frame ( maxWidth : . infinity , alignment : . leading )
. background ( Palette . raised , in : RoundedRectangle ( cornerRadius : 10 ))
. overlay ( RoundedRectangle ( cornerRadius : 10 ). stroke ( Palette . border ))
}
}
2026-10-17 10:54:06 -07:00
private struct StopReviewButton : View {
let reviewId : String
@ Environment ( Store . self ) private var store
@ Environment ( Overlays . self ) private var overlays
@ ViewState private var busy = false
var body : some View {
Button ( "Stop" ) {
busy = true
Task {
do {
_ = try await store . api . client . stopReview ( path : . init ( reviewId : reviewId )). ok
} catch {
busy = false
overlays . showAlert ( errorText ( error ))
}
}
}
. buttonStyle ( WebButtonStyle ( danger : true ))
. disabled ( busy )
. help ( "End the reviewer and record this review as failed" )
}
}
2026-10-17 07:26:18 -07:00
private struct ReviewResult : View {
let review : Review
let taskId : String ?
var body : some View {
let res = review . result ?. value ?? [:]
let fs = findings ( review )
let md = MDContext ( taskId : taskId )
VStack ( alignment : . leading , spacing : 8 ) {
MarkdownView ( blocks : parseMarkdown ( jsString ( res [ "summary" ] ?? "" )), ctx : md )
ReviewImages ( value : res [ "images" ] ?? nil )
if ! fs . isEmpty {
VStack ( alignment : . leading , spacing : 8 ) {
ForEach ( Array ( fs . enumerated ()), id : \ . offset ) { i , f in
FindingRow ( review : review , index : i , finding : f , taskId : taskId )
}
}
}
}
}
}
private struct FindingRow : View {
let review : Review
let index : Int
let finding : JSONDict
let taskId : String ?
var body : some View {
let f = finding
let severity = jsString ( f [ "severity" ] ?? "" )
let path = str ( f , "path" ) ?? ""
let line = max ( 0 , int ( f , "line" ) ?? 0 )
let endRaw = int ( f , "end_line" ) ?? 0
let end = endRaw > line ? endRaw : line
let whereText = path . isEmpty ? "" : " \( path )\( line > 0 ? ": \( line )\( end > line ? "– \( end ) " : "" ) " : "" ) "
let md = MDContext ( taskId : taskId )
VStack ( alignment : . leading , spacing : 4 ) {
HStack ( alignment : . firstTextBaseline , spacing : 8 ) {
Text ( severity )
. font ( Fonts . font ( 11 , weight : 700 ))
. foregroundStyle ( severity == "critical" || severity == "high" ? Palette . danger : severity == "medium" ? Palette . warn : Palette . muted )
if let category = str ( f , "category" ), ! category . isEmpty {
Text ( category ). font ( Fonts . font ( 11 )). foregroundStyle ( Palette . muted )
}
Text ( jsString ( f [ "title" ] ?? "" ))
. textSelection (. enabled )
. fixedSize ( horizontal : false , vertical : true )
if ! whereText . isEmpty {
Text ( whereText ). font ( Fonts . font ( 11 )). foregroundStyle ( Palette . muted ). textSelection (. enabled )
}
Spacer ( minLength : 0 )
if let taskId {
FindingActions ( taskId : taskId , review : review , index : index , finding : f , whereText : whereText )
}
}
if let taskId , ! path . isEmpty , line > 0 {
FindingDiff ( taskId : taskId , n : review . snapshotN , path : path , line : line , end : end )
}
MarkdownView ( blocks : parseMarkdown ( jsString ( f [ "detail" ] ?? "" )), ctx : md )
ReviewImages ( value : f [ "images" ] ?? nil )
}
. padding (. top , 6 )
. overlay ( alignment : . top ) {
Rectangle (). fill ( Palette . border ). frame ( height : 1 )
}
}
}
private struct ReviewImages : View {
let value : ( any Sendable )?
var body : some View {
let imgs = ( value as ? [( any Sendable )?] ?? []). map { $0 as ? JSONDict ?? [:] }
if ! imgs . isEmpty {
FlowLayout ( spacing : 10 ) {
ForEach ( Array ( imgs . enumerated ()), id : \ . offset ) { _ , img in
let blob = jsString ( img [ "blob" ] ?? "" )
let caption = str ( img , "caption" ) ?? ""
VStack ( alignment : . leading , spacing : 2 ) {
ByreImage ( source : . blob ( blob ), src : "/api/blobs/ \( blob ) " , maxHeight : 360 )
if ! caption . isEmpty {
Text ( caption ). font ( Fonts . font ( 11 )). foregroundStyle ( Palette . muted )
}
}
}
}
. padding (. vertical , 8 )
}
}
}
private struct FindingDiff : View {
let taskId : String
let n : Int
let path : String
let line : Int
let end : Int
@ Environment ( Store . self ) private var store
@ ViewState private var rows : [ DiffRow ]?
@ ViewState private var failure : String ?
var body : some View {
Group {
if let failure {
Text ( "diff not available: \( failure ) " )
. font ( Fonts . font ( 11 ))
. foregroundStyle ( Palette . muted )
. padding (. horizontal , 10 )
. padding (. vertical , 4 )
. frame ( maxWidth : . infinity , alignment : . leading )
. modifier ( DiffBox ())
} else if let rows , ! rows . isEmpty {
DiffLines ( rows : rows )
. modifier ( DiffBox ())
}
}
. task ( id : " \( taskId ) / \( n ) / \( path ) / \( line ) / \( end ) " ) {
2026-10-17 10:54:06 -07:00
failure = nil
2026-10-17 07:26:18 -07:00
do {
2026-10-17 07:43:33 -07:00
let patch = try await SnapshotPatches . patch ( store . api , taskId , n , path )
2026-10-17 07:26:18 -07:00
let lines = patch . map { linesAround ( $0 , line , end ) } ?? []
var out : [ DiffRow ] = []
for ( i , d ) in lines . enumerated () {
if i > 0 {
let prev = lines [ i - 1 ]
if d . kind != . del && prev . kind != . del && d . at > prev . at + 1 {
out . append ( DiffRow ( text : "⋯" , kind : . hunk ))
}
}
let mark = d . kind != . del && d . at >= line && d . at <= end
out . append ( DiffRow ( text : d . text , kind : d . kind , lineno : d . kind == . del ? "" : String ( d . at ), mark : mark ))
}
rows = out
} catch {
failure = errorText ( error )
}
}
}
}
private struct DiffBox : ViewModifier {
func body ( content : Content ) -> some View {
content
. background ( Palette . bg )
. clipShape ( RoundedRectangle ( cornerRadius : 6 ))
. overlay ( RoundedRectangle ( cornerRadius : 6 ). stroke ( Palette . border ))
. padding (. top , 2 )
}
}
private struct FindingActions : View {
let taskId : String
let review : Review
let index : Int
let finding : JSONDict
let whereText : String
@ Environment ( Store . self ) private var store
@ Environment ( Overlays . self ) private var overlays
@ ViewState private var done : FindingState ?
@ ViewState private var busy = false
var body : some View {
if let s = done ?? review . findingStates . additionalProperties [ String ( index )] {
Text ( s == . fixed ? "fix sent" : "ignored" )
. font ( Fonts . font ( 11 ))
. foregroundStyle ( Palette . muted )
} else {
HStack ( spacing : 6 ) {
Button ( "fix" ) {
fix ()
}
. buttonStyle ( SmallLinkButtonStyle ())
. help ( "Send this finding to the task's agent to fix" )
Button ( "ignore" ) {
ignore ()
}
. buttonStyle ( SmallLinkButtonStyle ())
. help ( "Mark this finding done without fixing it; Merge won't warn about it" )
}
. disabled ( busy )
}
}
private func mark ( _ s : FindingState ) async throws {
_ = try await store . api . client . setFindingState ( path : . init ( reviewId : review . id , index : index ), body : . json (. init ( state : s ))). ok
}
private func fix () {
let f = finding
let text = "Fix this finding from the review of snapshot # \( review . snapshotN ) : \n\n **[ \( jsString ( f [ "severity" ] ?? "" )) ] \( jsString ( f [ "title" ] ?? "" )) ** \( whereText . isEmpty ? "" : " (` \( whereText ) `)" ) \n\n \( jsString ( f [ "detail" ] ?? "" )) "
busy = true
Task {
do {
2026-10-18 07:30:00 -07:00
try await store . messageTask ( taskId , text )
2026-10-17 07:26:18 -07:00
} catch {
busy = false
overlays . showAlert ( errorText ( error ))
return
}
done = . fixed
do {
try await mark (. fixed )
} catch {
overlays . showAlert ( "fix sent, but not recorded: \( errorText ( error )) " )
}
}
}
private func ignore () {
busy = true
Task {
do {
try await mark (. ignored )
done = . ignored
} catch {
busy = false
overlays . showAlert ( errorText ( error ))
}
}
}
}