Make native app domain first

This commit is contained in:
saymrwulf 2026-04-28 13:32:16 +02:00
parent 56163922c0
commit 9704ac8452
6 changed files with 192 additions and 211 deletions

View file

@ -15,27 +15,12 @@ The first implementation is deliberately conservative:
## Quick Start ## Quick Start
```bash ```bash
./scripts/ratchet setup ./scripts/ratchet app
./scripts/ratchet
``` ```
`./scripts/ratchet` is the cockpit. It tells you exactly what to do next. This builds and opens the native macOS control room. Use the app for normal operation; terminal commands are advanced fallback tools.
If the cockpit is in cooldown and you want the app to wait until the earliest next action, run: The lifecycle state persists in `data/ratchet.sqlite`. If the app or Mac restarts, open the app again and it reads the same state.
```bash
./scripts/ratchet pipeline
```
It prints the exact monitor-only plan and asks `yes/no` before doing anything.
For the durable forever lifecycle supervisor:
```bash
./scripts/ratchet supervise
```
It persists lifecycle state in `data/ratchet.sqlite`. If the process crashes or the Mac reboots, start the same command again and it resumes from SQLite.
When you manually place a Braiins bid, record the exposure so the supervisor blocks new experiments: When you manually place a Braiins bid, record the exposure so the supervisor blocks new experiments:
@ -49,7 +34,7 @@ Close it only when finished:
./scripts/ratchet position close POSITION_ID ./scripts/ratchet position close POSITION_ID
``` ```
For the native macOS SwiftUI shell: For the native macOS app:
```bash ```bash
./scripts/ratchet app ./scripts/ratchet app
@ -57,9 +42,9 @@ For the native macOS SwiftUI shell:
This builds `macos/build/Braiins Ratchet.app` and opens the real app bundle. Do not use `swift run` for normal operation. This builds `macos/build/Braiins Ratchet.app` and opens the real app bundle. Do not use `swift run` for normal operation.
The app is a native visual control room: Mission Control, Research Map, Manual Exposure ledger, Reports, and a Ratchet Lecture. The design rationale is in `docs/APP_DESIGN_RESEARCH.md`. The app is a native visual control room: Mission Control, Research Map, Manual Exposure ledger, Advanced diagnostics, and a Ratchet Lecture. The design rationale is in `docs/APP_DESIGN_RESEARCH.md`.
For a 6-hour monitoring session: Advanced fallback for a 6-hour CLI monitoring session:
```bash ```bash
./scripts/ratchet watch 6 ./scripts/ratchet watch 6

View file

@ -1,18 +1,12 @@
# Start Here # Start Here
This project now has one operator entry point: This project now has one normal operator entry point:
```bash ```bash
./scripts/ratchet ./scripts/ratchet app
``` ```
That is the same as: That command builds and opens the native macOS app. The app is the control room. The terminal is only the launcher and fallback diagnostic path.
```bash
./scripts/ratchet next
```
It prints the cockpit: current state, exact next action, interpretation, reference commands, and ratchet rule.
## Your Job ## Your Job
@ -20,113 +14,43 @@ Your job is not to understand every metric.
Your job is: Your job is:
1. Run `./scripts/ratchet`. 1. Open the app with `./scripts/ratchet app`.
2. Do only what it says under `DO THIS NOW`. 2. Stay on `Mission Control` unless you intentionally need raw diagnostics.
3. Ignore every other command unless `DO THIS NOW` tells you to run it. 3. Read `Current Decision` first.
4. If it tells you to run `./scripts/ratchet watch 2`, start it, leave the terminal open, and come back after about 2 hours. 4. Read `Who Is In Control` second.
5. If you manually place a Braiins canary, write down the order details outside this repo and wait through the maturity window before judging it. 5. Use `Next Passive Action` only when it is enabled.
6. If you manually place a Braiins canary, record it in `Manual Exposure` immediately.
Do not start extra terminal watches while the app says a watch, cooldown, or manual exposure owns control.
## Who Is In Control? ## Who Is In Control?
If `watch` is running, the Python process is in control of that terminal. The app has one ownership model:
You do not need to babysit it. It will: 1. `The app is ready`: you may start the enabled passive action.
2. `A watch run owns control`: leave it alone until it finishes.
3. `Cooldown owns control`: wait until the shown earliest action time.
4. `Manual exposure owns control`: supervise the real-world Braiins/OCEAN position and do not start new experiments.
5. `The app is busy`: a monitor-only backend operation is running right now.
1. Collect samples every 5 minutes. This is the anti-babysitting rule: if the app says something else owns control, your workload is zero unless you are supervising a real manual exposure.
2. Write the run report when it finishes.
3. Print the cockpit again.
4. Return control to your shell prompt.
If you want the technical report, run `./scripts/ratchet report`. The normal workflow intentionally shows the cockpit first. ## What The App Does
After a watch finishes, the cockpit enters a post-watch cooldown. That is deliberate. The app is monitor-only. It never places, modifies, or cancels Braiins orders.
Post-watch cooldown means: It can:
1. The current experimental stage is complete. 1. Read persisted lifecycle state from `data/ratchet.sqlite`.
2. Starting another identical watch immediately is not useful ratcheting. 2. Collect OCEAN and public Braiins market samples.
3. The run report is the evidence artifact. 3. Run passive watch-only research windows.
4. The next planned touch is a later fresh sample, usually `./scripts/ratchet once`. 4. Write run reports under `reports/`.
5. Track manually executed Braiins exposure that you enter yourself.
During cooldown, the cockpit shows: 6. Resume from the same SQLite state after a crash or reboot.
1. A progress bar.
2. The earliest next action time.
3. The remaining minutes.
## Controlled Automation
If you do not want to babysit the cooldown manually, run:
```bash
./scripts/ratchet pipeline
```
The pipeline first prints a proposal like:
```text
I am going to: wait until this time, run one fresh sample, print the cockpit, then stop.
Are you OK with this? Type yes or no.
```
It only runs after you type `yes`.
It is still monitor-only. It never places, changes, or cancels Braiins orders.
## Forever Supervisor
For the full autoresearch lifecycle, run:
```bash
./scripts/ratchet supervise
```
The supervisor is the long-running engine. It:
1. Loads persisted state from `data/ratchet.sqlite`.
2. Waits through cooldown if cooldown is active.
3. Runs the next passive watch when due.
4. Writes reports and lifecycle events.
5. Re-enters cooldown.
6. Repeats until you stop it.
If it crashes or the Mac reboots, start the same command again. It resumes from SQLite.
Use this to inspect persisted state without starting the loop:
```bash
./scripts/ratchet supervise --status
```
## Manual Braiins Exposure
If you manually start a Braiins bid, record it immediately:
```bash
./scripts/ratchet position open --description "Braiins order abc, 0.0001 BTC, 3h canary" --maturity-hours 72
```
While a manual position is active:
1. The cockpit says `HOLD`.
2. The supervisor blocks new watch experiments.
3. Restarting the app keeps the manual exposure state.
List positions:
```bash
./scripts/ratchet position list
```
When the Braiins/OCEAN exposure is truly finished:
```bash
./scripts/ratchet position close POSITION_ID
```
## Native Mac App ## Native Mac App
The native SwiftUI shell is in: The native SwiftUI app is in:
```text ```text
macos/BraiinsRatchet macos/BraiinsRatchet
@ -140,34 +64,23 @@ Build and open the real app bundle:
This creates `macos/build/Braiins Ratchet.app`. After that, you can open that app bundle directly from Finder or pin it in the Dock. This creates `macos/build/Braiins Ratchet.app`. After that, you can open that app bundle directly from Finder or pin it in the Dock.
The app is a native cockpit over the same durable Python lifecycle engine.
The app includes controls to record and close manual exposure, but the same rule applies: it never places Braiins orders.
The app is organized as: The app is organized as:
1. `Mission Control`: one exact next action, cooldown, metrics, and direct watch-only controls. 1. `Mission Control`: current decision, control ownership, next passive action, progress, evidence, and plain English interpretation.
2. `Research Map`: visual autoresearch stage model. 2. `Research Map`: visual autoresearch stage model.
3. `Manual Exposure`: record or close manually executed Braiins exposure. 3. `Manual Exposure`: record or close manually executed Braiins exposure.
4. `Reports`: raw cockpit, report, and ledger artifacts. 4. `Advanced`: raw cockpit, report, and ledger artifacts for diagnostics.
5. `Ratchet Lecture`: the general observe, hypothesize, bound, mature, adapt method. 5. `Ratchet Lecture`: the general observe, hypothesize, bound, mature, adapt method.
## Research Pathway ## Research Pathway
The cockpit has two different time horizons: The app has three time horizons:
1. `DO THIS NOW` is the only command you should run next. 1. `Immediate`: what can happen now, usually start, wait, refresh, or hold.
2. `Ratchet Pathway Forecast` tells you what the next stages probably look like. 2. `Midterm`: what probably happens after the current watch, cooldown, or manual exposure matures.
3. `Longterm`: what could happen after multiple evidence artifacts point in the same direction.
The forecast is not a profit prediction. It is a workload and research-flow prediction. The pathway is allowed to change after each report. That is the point of ratcheting: the next stage adapts to measured evidence instead of following a rigid plan.
It is split into:
1. `Immediate`: what happens now.
2. `Midterm`: what probably happens after the current run or sample.
3. `Longterm`: what could happen after multiple reports mature.
Expect the pathway to change after each report. That is the point of ratcheting: the next stage adapts to measured evidence instead of following a rigid plan.
## What The Actions Mean ## What The Actions Mean
@ -177,7 +90,7 @@ Expect the pathway to change after each report. That is the point of ratcheting:
`manual_bid` means the stricter profit-seeking guardrails cleared. The code still does not place the order. You decide manually in Braiins. `manual_bid` means the stricter profit-seeking guardrails cleared. The code still does not place the order. You decide manually in Braiins.
## Where The Reports Are ## Where The Evidence Lives
The master ledger is: The master ledger is:
@ -191,12 +104,24 @@ Each completed watch creates one run report:
reports/run-*.md reports/run-*.md
``` ```
Older sessions can be embedded with: Use the app's `Advanced` tab when you need raw artifacts. Mission Control intentionally hides raw logs during normal operation.
## Advanced Fallback Commands
Use these only if the native app cannot be opened or you are debugging:
```bash ```bash
./scripts/ratchet retro START_UTC END_UTC ./scripts/ratchet
./scripts/ratchet once
./scripts/ratchet watch 2
./scripts/ratchet supervise
./scripts/ratchet position list
./scripts/ratchet report
./scripts/ratchet experiments
``` ```
The preferred workflow remains the native app.
## The Ratchet Rule ## The Ratchet Rule
One run is not a verdict. One run is a measurement. One run is not a verdict. One run is a measurement.

View file

@ -48,7 +48,7 @@ The SwiftUI app turns that into native surfaces:
- `Mission Control`: one exact action, cooldown, direct watch-only controls, and metrics. - `Mission Control`: one exact action, cooldown, direct watch-only controls, and metrics.
- `Research Map`: the ratchet pathway as a visual stage model. - `Research Map`: the ratchet pathway as a visual stage model.
- `Manual Exposure`: the ledger for real manually placed Braiins exposure. - `Manual Exposure`: the ledger for real manually placed Braiins exposure.
- `Reports`: raw artifacts kept available but no longer primary. - `Advanced`: raw artifacts kept available but no longer primary.
- `Ratchet Lecture`: a teachable model of observe, hypothesize, bound, mature, adapt. - `Ratchet Lecture`: a teachable model of observe, hypothesize, bound, mature, adapt.
## The Ratchet UX Rule ## The Ratchet UX Rule

View file

@ -1,8 +1,8 @@
# Braiins Ratchet Mac # Braiins Ratchet Mac
Native SwiftUI shell for the durable Braiins Ratchet lifecycle engine. Native SwiftUI control room for the durable Braiins Ratchet lifecycle engine.
The Python supervisor remains the source of truth. This app reads the same repository-local SQLite state through `./scripts/ratchet`. The Python lifecycle engine remains the source of truth. This app reads the same repository-local SQLite state through structured app state, not by making Mission Control a terminal transcript.
## Normal Run ## Normal Run
@ -19,10 +19,10 @@ This builds `macos/build/Braiins Ratchet.app` and opens the packaged app. Use th
- Research Map with the full ratchet pathway. - Research Map with the full ratchet pathway.
- Direct watch-only controls without an approval gate. - Direct watch-only controls without an approval gate.
- Manual exposure recording and closing controls. - Manual exposure recording and closing controls.
- Reports panel for raw artifacts. - Advanced panel for raw artifacts and backend diagnostics.
- Ratchet Lecture for the general autoresearch method. - Ratchet Lecture for the general autoresearch method.
- Monitor-only. It never places Braiins orders. - Monitor-only. It never places Braiins orders.
## Product Direction ## Product Direction
The next production step is wiring LaunchAgent controls for the durable supervisor. The next production step is wiring LaunchAgent controls for the durable supervisor while keeping Mission Control domain-first.

View file

@ -79,7 +79,6 @@ struct ContentView: View {
case .mission: case .mission:
MissionControlView( MissionControlView(
appState: appState, appState: appState,
transcript: transcript,
isRunning: isRunning, isRunning: isRunning,
glow: glow, glow: glow,
refresh: { Task { await refreshAppState() } }, refresh: { Task { await refreshAppState() } },
@ -98,8 +97,8 @@ struct ContentView: View {
close: closeManualExposure, close: closeManualExposure,
list: { Task { await runTextCommand(label: "position list", ["position", "list"], refreshAfterwards: true) } } list: { Task { await runTextCommand(label: "position list", ["position", "list"], refreshAfterwards: true) } }
) )
case .reports: case .advanced:
ReportsView( AdvancedView(
transcript: transcript, transcript: transcript,
lastCommand: lastCommand, lastCommand: lastCommand,
isRunning: isRunning, isRunning: isRunning,
@ -224,7 +223,7 @@ enum AppSection: String, CaseIterable, Identifiable {
case mission case mission
case map case map
case exposure case exposure
case reports case advanced
case lecture case lecture
var id: String { rawValue } var id: String { rawValue }
@ -234,7 +233,7 @@ enum AppSection: String, CaseIterable, Identifiable {
case .mission: "Mission Control" case .mission: "Mission Control"
case .map: "Research Map" case .map: "Research Map"
case .exposure: "Manual Exposure" case .exposure: "Manual Exposure"
case .reports: "Reports" case .advanced: "Advanced"
case .lecture: "Ratchet Lecture" case .lecture: "Ratchet Lecture"
} }
} }
@ -244,7 +243,7 @@ enum AppSection: String, CaseIterable, Identifiable {
case .mission: "scope" case .mission: "scope"
case .map: "point.3.connected.trianglepath.dotted" case .map: "point.3.connected.trianglepath.dotted"
case .exposure: "lock.shield" case .exposure: "lock.shield"
case .reports: "doc.text.magnifyingglass" case .advanced: "wrench.and.screwdriver"
case .lecture: "graduationcap" case .lecture: "graduationcap"
} }
} }
@ -252,7 +251,6 @@ enum AppSection: String, CaseIterable, Identifiable {
struct MissionControlView: View { struct MissionControlView: View {
let appState: AppStatePayload? let appState: AppStatePayload?
let transcript: String
let isRunning: Bool let isRunning: Bool
let glow: Bool let glow: Bool
let refresh: () -> Void let refresh: () -> Void
@ -268,6 +266,7 @@ struct MissionControlView: View {
VStack(spacing: 14) { VStack(spacing: 14) {
AutoresearchOrb(phase: ResearchPhase.from(appState), glow: glow) AutoresearchOrb(phase: ResearchPhase.from(appState), glow: glow)
.frame(height: 250) .frame(height: 250)
ControlOwnershipCard(appState: appState, isRunning: isRunning)
PassiveRunCard( PassiveRunCard(
plan: appState?.automationPlan, plan: appState?.automationPlan,
isRunning: isRunning, isRunning: isRunning,
@ -277,9 +276,9 @@ struct MissionControlView: View {
.frame(width: 350) .frame(width: 350)
} }
MetricsDeck(appState: appState) EvidenceDeck(appState: appState)
ResearchTimeline(appState: appState, compact: false) ResearchTimeline(appState: appState, compact: false)
PlainEnglishCard(appState: appState, transcript: transcript) PlainEnglishCard(appState: appState)
} }
.padding(28) .padding(28)
} }
@ -312,7 +311,7 @@ struct HeroPanel: View {
} }
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
Text("Do This Now") Text("Current Decision")
.font(.caption.weight(.heavy)) .font(.caption.weight(.heavy))
.textCase(.uppercase) .textCase(.uppercase)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
@ -322,17 +321,6 @@ struct HeroPanel: View {
Text(directive.detail) Text(directive.detail)
.font(.title3.weight(.semibold)) .font(.title3.weight(.semibold))
.foregroundStyle(.primary) .foregroundStyle(.primary)
if let command = directive.command {
HStack(spacing: 10) {
Image(systemName: "terminal")
Text(command)
.font(.system(.body, design: .monospaced).weight(.semibold))
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.black.opacity(0.18), in: RoundedRectangle(cornerRadius: 14, style: .continuous))
}
} }
if let watch = appState?.operatorState.completedWatch { if let watch = appState?.operatorState.completedWatch {
@ -345,6 +333,53 @@ struct HeroPanel: View {
} }
} }
struct ControlOwnershipCard: View {
let appState: AppStatePayload?
let isRunning: Bool
var body: some View {
GlassPanel(padding: 16) {
VStack(alignment: .leading, spacing: 10) {
Label("Who Is In Control", systemImage: symbol)
.font(.headline)
Text(title)
.font(.title3.weight(.bold))
Text(detail)
.font(.callout)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
private var symbol: String {
if isRunning { return "gearshape.2" }
guard let state = appState?.operatorState else { return "questionmark.circle" }
if state.activeWatch != nil { return "binoculars" }
if !state.activeManualPositions.isEmpty { return "lock.shield" }
if state.completedWatch != nil { return "timer" }
return "scope"
}
private var title: String {
if isRunning { return "The app is busy" }
guard let state = appState?.operatorState else { return "Loading state" }
if state.activeWatch != nil { return "A watch run owns control" }
if !state.activeManualPositions.isEmpty { return "Manual exposure owns control" }
if state.completedWatch != nil { return "Cooldown owns control" }
return "The app is ready"
}
private var detail: String {
if isRunning { return "A monitor-only operation is running. Do not start a competing action." }
guard let state = appState?.operatorState else { return "Reading the lifecycle database." }
if state.activeWatch != nil { return "Let the watch finish; duplicate watches corrupt the research trail." }
if !state.activeManualPositions.isEmpty { return "A real-world position is active, so new experiments stay blocked." }
if let watch = state.completedWatch { return "Wait until \(watch.earliestActionLocal) before the next useful sample." }
return "No active watch, no manual exposure, and no cooldown block."
}
}
struct PassiveRunCard: View { struct PassiveRunCard: View {
let plan: AutomationPlanPayload? let plan: AutomationPlanPayload?
let isRunning: Bool let isRunning: Bool
@ -353,7 +388,7 @@ struct PassiveRunCard: View {
var body: some View { var body: some View {
GlassPanel { GlassPanel {
VStack(alignment: .leading, spacing: 14) { VStack(alignment: .leading, spacing: 14) {
Label("Watch-only Control", systemImage: "binoculars") Label("Next Passive Action", systemImage: "arrow.forward.circle")
.font(.headline) .font(.headline)
Text(title) Text(title)
.font(.title3.weight(.bold)) .font(.title3.weight(.bold))
@ -419,7 +454,7 @@ struct PassiveRunCard: View {
switch plan.kind { switch plan.kind {
case "once_now": return "Refresh Now" case "once_now": return "Refresh Now"
case "watch_2h": return "Start Watch-only Run" case "watch_2h": return "Start Watch-only Run"
case "wait_then_once": return plan.waitSeconds > 0 ? "Wait" : "Refresh Now" case "wait_then_once": return plan.waitSeconds > 0 ? "Cooldown Active" : "Refresh Now"
case "report_only": return "Open Report" case "report_only": return "Open Report"
default: return "Refresh State" default: return "Refresh State"
} }
@ -438,43 +473,50 @@ struct PassiveRunCard: View {
} }
} }
struct MetricsDeck: View { struct EvidenceDeck: View {
let appState: AppStatePayload? let appState: AppStatePayload?
var body: some View { var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 14), count: 4), spacing: 14) { LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 14), count: 4), spacing: 14) {
MetricTile( MetricTile(
title: "Market Freshness", title: "Braiins Market",
value: freshnessText, value: marketPrice,
detail: appState?.operatorState.latestMarketTimestamp ?? "no sample", detail: marketDetail,
symbol: "clock" symbol: "chart.line.uptrend.xyaxis"
) )
MetricTile( MetricTile(
title: "Strategy Action", title: "Model Net",
value: appState?.operatorState.action ?? "none", value: proposalValue("expected_net_btc", fallback: "n/a"),
detail: actionDetail, detail: actionDetail,
symbol: "target" symbol: "plus.forwardslash.minus"
) )
MetricTile( MetricTile(
title: "Manual Exposure", title: "OCEAN Pool",
value: exposureText, value: oceanValue("pool_hashrate_eh_s", suffix: " EH/s"),
detail: "Blocks new experiments while active", detail: oceanValue("network_difficulty_t", prefix: "difficulty "),
symbol: "shield.lefthalf.filled" symbol: "water.waves"
) )
MetricTile( MetricTile(
title: "Latest Report", title: "Evidence",
value: appState?.operatorState.latestReport?.lastPathComponent ?? "none", value: evidenceValue,
detail: appState?.operatorState.latestReport ?? "No artifact yet", detail: evidenceDetail,
symbol: "doc.text" symbol: "archivebox"
) )
} }
} }
private var freshnessText: String { private var marketPrice: String {
guard let state = appState?.operatorState else { return "loading" } if let fillable = appState?.latest.market?["fillable_price_btc_per_eh_day"]?.description, fillable != "n/a" {
if state.isFresh { return "fresh" } return fillable
if let minutes = state.freshnessMinutes { return "stale \(minutes)m" } }
return "unknown" return appState?.latest.market?["best_ask_btc_per_eh_day"]?.description ?? "n/a"
}
private var marketDetail: String {
let freshness = appState?.operatorState.freshnessMinutes.map { "\($0)m old" } ?? "age unknown"
let ask = appState?.latest.market?["best_ask_btc_per_eh_day"]?.description ?? "n/a"
let last = appState?.latest.market?["last_price_btc_per_eh_day"]?.description ?? "n/a"
return "\(freshness), ask \(ask), last \(last)"
} }
private var actionDetail: String { private var actionDetail: String {
@ -484,9 +526,26 @@ struct MetricsDeck: View {
return "No useful market action" return "No useful market action"
} }
private var exposureText: String { private var evidenceValue: String {
let count = appState?.operatorState.activeManualPositions.count ?? 0 let count = appState?.operatorState.activeManualPositions.count ?? 0
return count == 0 ? "none" : "\(count) active" if count > 0 { return "\(count) active exposure" }
return appState?.operatorState.latestReport?.lastPathComponent ?? "none"
}
private var evidenceDetail: String {
if let watch = appState?.operatorState.completedWatch {
return "cooldown \(watch.remainingMinutes)m remaining"
}
return appState?.operatorState.latestReport ?? "No artifact yet"
}
private func proposalValue(_ key: String, fallback: String) -> String {
appState?.latest.proposal?[key]?.description ?? fallback
}
private func oceanValue(_ key: String, prefix: String = "", suffix: String = "") -> String {
guard let value = appState?.latest.ocean?[key]?.description else { return "n/a" }
return "\(prefix)\(value)\(suffix)"
} }
} }
@ -617,7 +676,6 @@ struct TimelineNode: View {
struct PlainEnglishCard: View { struct PlainEnglishCard: View {
let appState: AppStatePayload? let appState: AppStatePayload?
let transcript: String
var body: some View { var body: some View {
GlassPanel { GlassPanel {
@ -799,7 +857,7 @@ struct ActiveExposureList: View {
} }
} }
struct ReportsView: View { struct AdvancedView: View {
let transcript: String let transcript: String
let lastCommand: String let lastCommand: String
let isRunning: Bool let isRunning: Bool
@ -811,9 +869,9 @@ struct ReportsView: View {
VStack(alignment: .leading, spacing: 18) { VStack(alignment: .leading, spacing: 18) {
HStack { HStack {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("Reports") Text("Advanced")
.font(.system(size: 36, weight: .black, design: .rounded)) .font(.system(size: 36, weight: .black, design: .rounded))
Text("Raw artifacts remain here, away from the noob cockpit.") Text("Raw artifacts and backend diagnostics live here, away from Mission Control.")
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
Spacer() Spacer()
@ -824,7 +882,7 @@ struct ReportsView: View {
GlassPanel { GlassPanel {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
Label(lastCommand, systemImage: "terminal") Label(lastCommand, systemImage: "wrench.and.screwdriver")
.font(.headline) .font(.headline)
ScrollView { ScrollView {
Text(transcript) Text(transcript)
@ -1095,37 +1153,35 @@ enum ResearchPhase {
struct Directive { struct Directive {
let title: String let title: String
let detail: String let detail: String
let command: String?
let color: Color let color: Color
static func from(_ appState: AppStatePayload?) -> Directive { static func from(_ appState: AppStatePayload?) -> Directive {
guard let appState else { guard let appState else {
return Directive(title: "LOAD STATE", detail: "The app is reading the ratchet lifecycle database.", command: nil, color: .secondary) return Directive(title: "LOAD STATE", detail: "The app is reading the ratchet lifecycle database.", color: .secondary)
} }
if let watch = appState.operatorState.completedWatch { if let watch = appState.operatorState.completedWatch {
return Directive( return Directive(
title: "STOP", title: "STOP",
detail: "Wait until \(watch.earliestActionLocal). Repeating the same watch now would be loop-chasing.", detail: "Wait until \(watch.earliestActionLocal). Repeating the same watch now would be loop-chasing.",
command: "./scripts/ratchet once",
color: .orange color: .orange
) )
} }
if appState.operatorState.activeWatch != nil { if appState.operatorState.activeWatch != nil {
return Directive(title: "WAIT", detail: "A watch is already running. Do not start another one.", command: nil, color: .orange) return Directive(title: "WAIT", detail: "A watch is already running. Do not start another one.", color: .orange)
} }
if !appState.operatorState.activeManualPositions.isEmpty { if !appState.operatorState.activeManualPositions.isEmpty {
return Directive(title: "HOLD", detail: "Manual Braiins exposure is active. Supervise it; do not start new experiments.", command: "./scripts/ratchet supervise --status", color: .orange) return Directive(title: "HOLD", detail: "Manual Braiins exposure is active. Supervise it; do not start new experiments.", color: .orange)
} }
if !appState.operatorState.hasOcean || !appState.operatorState.hasMarket || !appState.operatorState.isFresh { if !appState.operatorState.hasOcean || !appState.operatorState.hasMarket || !appState.operatorState.isFresh {
return Directive(title: "REFRESH", detail: "The latest market state is stale or missing. Collect exactly one fresh sample.", command: "./scripts/ratchet once", color: .green) return Directive(title: "REFRESH", detail: "The latest market state is stale or missing. Collect exactly one fresh sample.", color: .green)
} }
if appState.operatorState.action == "manual_canary" { if appState.operatorState.action == "manual_canary" {
return Directive(title: "WATCH", detail: "Run one bounded passive watch. This buys information, not a promise of profit.", command: "./scripts/ratchet watch 2", color: .green) return Directive(title: "WATCH", detail: "Run one bounded passive watch. This buys information, not a promise of profit.", color: .green)
} }
if appState.operatorState.action == "manual_bid" { if appState.operatorState.action == "manual_bid" {
return Directive(title: "REVIEW", detail: "Read the full report before any manual Braiins action.", command: "./scripts/ratchet report", color: .green) return Directive(title: "REVIEW", detail: "Read the full report before any manual Braiins action.", color: .green)
} }
return Directive(title: "OBSERVE", detail: "No useful action window is visible right now.", command: nil, color: .secondary) return Directive(title: "OBSERVE", detail: "No useful action window is visible right now.", color: .secondary)
} }
} }

View file

@ -45,6 +45,16 @@ class MacAppPackagingTest(unittest.TestCase):
self.assertIn("./scripts/ratchet app", text) self.assertIn("./scripts/ratchet app", text)
self.assertNotIn("swift run BraiinsRatchetMac", text) self.assertNotIn("swift run BraiinsRatchetMac", text)
def test_start_here_is_app_first_and_not_pipeline_first(self):
text = (ROOT / "START_HERE.md").read_text()
self.assertIn("This project now has one normal operator entry point", text)
self.assertIn("./scripts/ratchet app", text)
self.assertIn("The app is the control room", text)
self.assertIn("Who Is In Control", text)
self.assertNotIn("Controlled Automation", text)
self.assertNotIn("./scripts/ratchet pipeline", text)
def test_swift_app_uses_native_dashboard_not_raw_terminal_as_primary_ui(self): def test_swift_app_uses_native_dashboard_not_raw_terminal_as_primary_ui(self):
source = ROOT / "macos" / "BraiinsRatchet" / "Sources" / "BraiinsRatchetMac" / "BraiinsRatchetApp.swift" source = ROOT / "macos" / "BraiinsRatchet" / "Sources" / "BraiinsRatchetMac" / "BraiinsRatchetApp.swift"
text = source.read_text() text = source.read_text()
@ -56,6 +66,11 @@ class MacAppPackagingTest(unittest.TestCase):
self.assertIn("AppStatePayload", text) self.assertIn("AppStatePayload", text)
self.assertIn("loadAppState", text) self.assertIn("loadAppState", text)
self.assertIn("PassiveRunCard", text) self.assertIn("PassiveRunCard", text)
self.assertIn("ControlOwnershipCard", text)
self.assertIn("EvidenceDeck", text)
self.assertIn("AdvancedView", text)
self.assertIn("Current Decision", text)
self.assertNotIn("Do This Now", text)
self.assertNotIn("Automation Gate", text) self.assertNotIn("Automation Gate", text)
self.assertNotIn("confirmationDialog", text) self.assertNotIn("confirmationDialog", text)
self.assertNotIn("showAutomationApproval", text) self.assertNotIn("showAutomationApproval", text)