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// ManualView.swift3// Metrika4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.8//910import SwiftUI11import ZQEngine1213/// User manual pane: a searchable reference of every command (rendered14/// from the same registry as the console's `help`), plus an overview of15/// how Metrika works.16struct ManualPane: View {17 @State private var searchText = ""18 @State private var selection: String? = ManualPane.overviewID1920 static let overviewID = "__overview"2122 private var filteredDocs: [ZQCommandDoc] {23 let needle = searchText.trimmingCharacters(in: .whitespaces).lowercased()24 guard !needle.isEmpty else { return ZQCommandReference.all }25 return ZQCommandReference.all.filter {26 $0.verb.lowercased().contains(needle)27 || $0.summary.lowercased().contains(needle)28 || $0.category.lowercased().contains(needle)29 }30 }3132 var body: some View {33 HSplitView {34 VStack(spacing: 0) {35 HStack(spacing: 6) {36 Image(systemName: "magnifyingglass")37 .foregroundStyle(.secondary)38 TextField("Search commands", text: $searchText)39 .textFieldStyle(.plain)40 }41 .padding(8)42 Divider()43 List(selection: $selection) {44 if searchText.isEmpty {45 Label("How Metrika works", systemImage: "info.circle")46 .tag(Self.overviewID)47 }48 ForEach(ZQCommandReference.categories, id: \.self) { category in49 let docs = filteredDocs.filter { $0.category == category }50 if !docs.isEmpty {51 Section(category) {52 ForEach(docs) { doc in53 Text(doc.verb)54 .fontDesign(.monospaced)55 .tag(doc.verb)56 }57 }58 }59 }60 }61 .listStyle(.sidebar)62 }63 .frame(minWidth: 200, maxWidth: 280)6465 ScrollView {66 Group {67 if selection == Self.overviewID {68 OverviewPage()69 } else if let doc = ZQCommandReference.all.first(where: {70 $0.verb == selection71 }) {72 CommandPage(doc: doc)73 } else {74 ContentUnavailableView(75 "Select a command", systemImage: "book",76 description: Text("Pick a topic from the list.")77 )78 }79 }80 .frame(maxWidth: 640, alignment: .leading)81 .padding(24)82 }83 .frame(maxWidth: .infinity)84 }85 }86}8788/// One command's reference page.89private struct CommandPage: View {90 let doc: ZQCommandDoc9192 var body: some View {93 VStack(alignment: .leading, spacing: 14) {94 HStack(alignment: .firstTextBaseline, spacing: 10) {95 Text(doc.verb)96 .font(.title2.weight(.semibold))97 .fontDesign(.monospaced)98 if let abbreviation = doc.abbreviation {99 Text("abbreviation: \(abbreviation)")100 .font(.caption)101 .foregroundStyle(.secondary)102 }103 Spacer()104 Text(doc.category)105 .font(.caption)106 .padding(.horizontal, 8)107 .padding(.vertical, 3)108 .background(.quaternary, in: Capsule())109 }110111 Text(doc.summary)112113 GroupBox("Syntax") {114 Text(doc.syntax)115 .fontDesign(.monospaced)116 .font(.system(size: 12))117 .textSelection(.enabled)118 .frame(maxWidth: .infinity, alignment: .leading)119 .padding(4)120 }121122 if !doc.options.isEmpty {123 GroupBox("Options") {124 VStack(alignment: .leading, spacing: 6) {125 ForEach(doc.options, id: \.name) { option in126 HStack(alignment: .firstTextBaseline, spacing: 12) {127 Text(option.name)128 .fontDesign(.monospaced)129 .font(.system(size: 12))130 .frame(width: 130, alignment: .leading)131 Text(option.meaning)132 .font(.system(size: 12))133 }134 }135 }136 .frame(maxWidth: .infinity, alignment: .leading)137 .padding(4)138 }139 }140141 if !doc.examples.isEmpty {142 GroupBox("Examples") {143 VStack(alignment: .leading, spacing: 4) {144 ForEach(doc.examples, id: \.self) { example in145 Text(". \(example)")146 .fontDesign(.monospaced)147 .font(.system(size: 12))148 .textSelection(.enabled)149 }150 }151 .frame(maxWidth: .infinity, alignment: .leading)152 .padding(4)153 }154 }155156 if let notes = doc.notes {157 Text(notes)158 .font(.callout)159 .foregroundStyle(.secondary)160 }161 }162 }163}164165/// "How Metrika works" — the architecture from a user's point of view.166private struct OverviewPage: View {167 var body: some View {168 VStack(alignment: .leading, spacing: 16) {169 Text("How Metrika works")170 .font(.title2.weight(.semibold))171172 section("The command line", """173 Everything in Metrika is a command, following one grammar:174175 command [varlist] [if condition] [in range] [, options]176177 Type commands in the Console pane (↑/↓ recall history), run whole \178 scripts from the Do-file pane (⌘R runs the selection or the file), \179 or filter the Data pane — its filter bar compiles to the same `if` \180 expressions. All three routes share one execution engine, so a \181 do-file replays exactly what you typed.182 """)183184 section("Data", """185 `use` loads parquet, csv, json, arrow, or Stata .dta files; `save` \186 writes them back. Data is columnar in memory (DuckDB underneath), \187 numeric values are 64-bit floats, and missing values are tracked \188 explicitly — every estimator reports how many observations \189 listwise deletion dropped. The sidebar always shows the working \190 dataset's variables and missing counts.191 """)192193 section("Estimation", """194 Estimators follow Stata conventions: `reg y x, robust \195 cluster(id)` gives HC1 or cluster-robust standard errors, factor \196 variables expand with `i.var`, and every CPU result is validated \197 against R to 1e-10. After fitting, `predict` generates fitted \198 values or residuals and `margins, dydx()` computes average \199 marginal effects with delta-method standard errors.200 """)201202 section("The GPU, invisibly", """203 A planner decides where each command runs — you never choose. \204 Large bootstrap runs execute batched on the Apple GPU; small jobs \205 stay on the CPU. Random numbers come from a counter-based Philox \206 generator, so `set seed 42` produces bit-identical resamples on \207 either backend, in any chunk order.208 """)209210 section("Extending Metrika", """211 Drop a .zyq script into ~/Library/Application Support/Metrika/\212 Commands/ and its filename becomes a command — an optional \213 `args name…` first line names the arguments, referenced as \214 `name' in the body. Native plugins are Swift types implementing \215 ZQCommandPlugin (see `help zscore` for the shipped example).216 """)217218 section("Reproducibility", """219 `log using file` records a session. Seeds are honored across CPU \220 and GPU. Do-files replay top to bottom with the errors pointing \221 at the failing line. The same engine powers the metrika-cli \222 terminal tool, so pipelines can run headless.223 """)224 }225 }226227 private func section(_ title: String, _ body: String) -> some View {228 VStack(alignment: .leading, spacing: 6) {229 Text(title).font(.headline)230 Text(body).font(.callout)231 }232 }233}234