Merge nucleic/amber-umber-falcon-aa9i into dev
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import Foundation
|
||||
import NucleicProtocol
|
||||
|
||||
/// One row of the transcript as *displayed*: a projected item, or a run of adjacent file-lock
|
||||
/// notes gathered into a single row.
|
||||
///
|
||||
/// This layer exists for the Orchestra supervisor case. `AppStore` emits waiting/acquired/released
|
||||
/// notes for every worker's edit attributed to the supervisor (the lock *holder*), but a supervisor
|
||||
/// edits nothing itself, so those notes have no edit card for the projection to fold them onto and
|
||||
/// each one arrives as its own standalone `.note`. A fan-out over a few dozen files therefore
|
||||
/// renders as a few dozen near-identical rows — each with the transcript's full 14pt inter-row gap
|
||||
/// — burying the conversation between them.
|
||||
///
|
||||
/// Gathering them here rather than in `TranscriptProjection` is deliberate: the projection is the
|
||||
/// canonical, cross-platform fold, bound by the incremental projector's
|
||||
/// `fold(prefix) ++ fold(tail) == fold(whole)` invariant and by the exhaustive `TranscriptItem.Kind`
|
||||
/// switch that feeds the Windows renderer through `TranscriptProjectionABI`. Grouping purely for
|
||||
/// readability belongs on the far side of both. Pure model code (Foundation + NucleicProtocol, no
|
||||
/// SwiftUI) so it builds into the host-testable `NucleicRemoteProjection` target.
|
||||
struct TranscriptDisplayRow: Identifiable, Equatable {
|
||||
enum Kind: Equatable {
|
||||
/// A projected row, passed through untouched.
|
||||
case item(TranscriptItem)
|
||||
/// Adjacent lock-lifecycle notes shown as one row — literal lines while the trail is
|
||||
/// short, a collapsed `LockDigest` once it isn't. Kept in stream order, so the folded
|
||||
/// view sees exactly what the individual rows would have said.
|
||||
case lockRun([NoteLock])
|
||||
}
|
||||
|
||||
let id: String
|
||||
let seq: UInt64
|
||||
let kind: Kind
|
||||
}
|
||||
|
||||
enum TranscriptDisplay {
|
||||
|
||||
/// Gathers each maximal run of adjacent, path-carrying lock notes into one row, passing
|
||||
/// everything else through unchanged.
|
||||
///
|
||||
/// Runs of one are gathered too: a lone lock moment then renders through the same
|
||||
/// `LockLineRow` the folded card lines use, so every lock in the transcript reads the same way
|
||||
/// whether it landed on an edit card or on its own.
|
||||
///
|
||||
/// One deliberate exclusion — a lock note carrying no structured `NoteLock` (the re-ground and
|
||||
/// update-conflict notes) is prose the user may have to act on. It never joins a run, *and* it
|
||||
/// breaks the run around it rather than being stepped over to splice the two halves together.
|
||||
static func rows(_ items: [TranscriptItem]) -> [TranscriptDisplayRow] {
|
||||
var rows: [TranscriptDisplayRow] = []
|
||||
rows.reserveCapacity(items.count)
|
||||
var run: [TranscriptItem] = []
|
||||
|
||||
func flushRun() {
|
||||
defer { run.removeAll(keepingCapacity: true) }
|
||||
guard let first = run.first else { return }
|
||||
// Keyed on the run's first row, so a growing run extends the same row instead of
|
||||
// being replaced by a differently-named one — which would drop the reader's expansion
|
||||
// and jog the scroll anchor as a live fan-out streams its locks in.
|
||||
rows.append(.init(id: "lockrun-\(first.id)", seq: first.seq,
|
||||
kind: .lockRun(run.compactMap(structuredLock))))
|
||||
}
|
||||
|
||||
for item in items {
|
||||
if structuredLock(item) != nil {
|
||||
run.append(item)
|
||||
} else {
|
||||
flushRun()
|
||||
rows.append(.init(id: item.id, seq: item.seq, kind: .item(item)))
|
||||
}
|
||||
}
|
||||
flushRun()
|
||||
return rows
|
||||
}
|
||||
|
||||
/// The structured lock a note carries, or nil for anything that isn't a foldable lock moment.
|
||||
/// A lock note with no paths yields nothing to gather, so it stays an ordinary row; so does a
|
||||
/// process-stall alert, which is a live control surface (Kill / Keep waiting), not a log line.
|
||||
private static func structuredLock(_ item: TranscriptItem) -> NoteLock? {
|
||||
guard case .note(_, _, let lockEvent, let lock, let processStall) = item.kind,
|
||||
lockEvent, processStall == nil, let lock, !lock.paths.isEmpty else { return nil }
|
||||
return lock
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user