spb/metrika Public
Stata-class statistics, GPU-accelerated by Apple Silicon. Native Swift — no Electron, no Python runtime, no compromises.
Swift 92.4%
HTML 3.3%
R 3%
Shell 1.3%
1//2// ConsoleView.swift3// Metrika4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.8//910import SwiftUI1112/// Command console: scrollback of executed commands with their rendered13/// output, plus an input line with ↑/↓ history recall (CLAUDE.md §7).14struct ConsoleView: View {15 @Environment(SessionModel.self) private var model16 @State private var input = ""17 @State private var historyCursor: Int?18 @FocusState private var inputFocused: Bool1920 var body: some View {21 VStack(spacing: 0) {22 ScrollViewReader { proxy in23 ScrollView {24 LazyVStack(alignment: .leading, spacing: 10) {25 ForEach(model.entries) { entry in26 VStack(alignment: .leading, spacing: 2) {27 if !entry.command.isEmpty {28 Text(". \(entry.command)")29 .foregroundStyle(.secondary)30 }31 if !entry.output.isEmpty {32 Text(entry.output)33 .foregroundStyle(34 entry.isError ? Color.red : Color.primary35 )36 .textSelection(.enabled)37 }38 }39 .id(entry.id)40 }41 }42 .padding(12)43 .frame(maxWidth: .infinity, alignment: .leading)44 }45 .onChange(of: model.entries) {46 if let last = model.entries.last {47 proxy.scrollTo(last.id, anchor: .bottom)48 }49 }50 }5152 Divider()5354 HStack(spacing: 8) {55 Text(".")56 .foregroundStyle(.secondary)57 TextField("Type a command — e.g. use sales.parquet", text: $input)58 .textFieldStyle(.plain)59 .focused($inputFocused)60 .onSubmit(submit)61 .onKeyPress(.upArrow) { recallHistory(step: -1) }62 .onKeyPress(.downArrow) { recallHistory(step: 1) }63 if model.isRunning {64 ProgressView()65 .controlSize(.small)66 }67 }68 .padding(10)69 }70 .fontDesign(.monospaced)71 .font(.system(size: 12))72 .onAppear { inputFocused = true }73 }7475 private func submit() {76 let command = input77 input = ""78 historyCursor = nil79 Task { await model.run(command) }80 }8182 private func recallHistory(step: Int) -> KeyPress.Result {83 let history = model.history84 guard !history.isEmpty else { return .ignored }85 var cursor = historyCursor ?? history.count86 cursor = min(max(cursor + step, 0), history.count)87 historyCursor = cursor88 input = cursor < history.count ? history[cursor] : ""89 return .handled90 }91}9293/// Sidebar: variables of the working dataset with type and missing count94/// (CLAUDE.md §7, pane 5).95struct VariablesSidebar: View {96 @Environment(SessionModel.self) private var model9798 var body: some View {99 List {100 Section("Variables (\(model.observationCount) obs)") {101 ForEach(model.variables) { variable in102 HStack {103 Text(variable.name)104 .fontDesign(.monospaced)105 Spacer()106 if variable.missingCount > 0 {107 Text("\(variable.missingCount) mi")108 .foregroundStyle(.orange)109 .font(.caption)110 }111 Text(variable.type)112 .foregroundStyle(.secondary)113 .font(.caption)114 }115 }116 }117 }118 .listStyle(.sidebar)119 }120}121