Redesign native research cockpit

This commit is contained in:
saymrwulf 2026-04-28 00:19:53 +02:00
parent 427045e634
commit 3d328752c3
8 changed files with 1349 additions and 214 deletions

View file

@ -57,6 +57,8 @@ 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.
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`.
For a 6-hour monitoring session:
```bash

View file

@ -144,6 +144,14 @@ 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:
1. `Mission Control`: one exact next action, cooldown, metrics, and automation approval.
2. `Research Map`: visual autoresearch stage model.
3. `Manual Exposure`: record or close manually executed Braiins exposure.
4. `Reports`: raw cockpit, report, and ledger artifacts.
5. `Ratchet Lecture`: the general observe, hypothesize, bound, mature, adapt method.
## Research Pathway
The cockpit has two different time horizons:

View file

@ -0,0 +1,64 @@
# Native App Design Research
This app is not supposed to be a prettier terminal. It is supposed to be a control room for a risky, long-running research lifecycle.
## Design Sources
Apple's Liquid Glass guidance emphasizes system-native structure before visual effects:
- Use standard SwiftUI/AppKit structure so controls, navigation, sheets, and toolbars inherit system behavior.
- Keep navigation and controls in a distinct functional layer above the content.
- Avoid overusing custom glass effects; too much glass becomes noise.
- Support arbitrary window sizes with split views.
- Preserve accessibility when transparency or motion is reduced.
Source: <https://developer.apple.com/documentation/TechnologyOverviews/adopting-liquid-glass>
Microsoft's Human-AI Interaction Guidelines are directly relevant because this app makes recommendations under uncertainty:
- Make clear what the system can and cannot do.
- Make clear how well it can do it.
- Show contextually relevant information.
- Explain why the system did what it did.
- Support correction, dismissal, global controls, and cautious adaptation over time.
Source: <https://www.microsoft.com/en-us/research/publication/guidelines-for-human-ai-interaction/>
Nielsen Norman's usability heuristics matter because the operator may be tired, confused, or dealing with real money:
- Show system status.
- Use real-world language.
- Prevent errors before they happen.
- Prefer recognition over recall.
- Provide clear recovery paths.
Source: <https://media.nngroup.com/media/articles/attachments/Heuristic_Summary1-compressed.pdf>
## Product Decisions
The native app now treats the Python engine as a structured state provider, not as a terminal to embed. The new `app-state` command returns JSON with:
- Current operator state.
- Automation plan.
- Cockpit text for audit/debug.
- Latest OCEAN, Braiins, and strategy proposal payloads.
The SwiftUI app turns that into native surfaces:
- `Mission Control`: one exact action, cooldown, approval gate, and metrics.
- `Research Map`: the ratchet pathway as a visual stage model.
- `Manual Exposure`: the ledger for real manually placed Braiins exposure.
- `Reports`: raw artifacts kept available but no longer primary.
- `Ratchet Lecture`: a teachable model of observe, hypothesize, bound, mature, adapt.
## The Ratchet UX Rule
The app must always answer these questions without forcing the user to parse logs:
1. Who is in control right now?
2. What is the earliest useful next action?
3. What evidence artifact exists?
4. What action is blocked for safety?
5. Which single knob, if any, is eligible for later adaptation?
If the app cannot answer those questions graphically and in plain language, it is failing its purpose.

View file

@ -14,10 +14,13 @@ This builds `macos/build/Braiins Ratchet.app` and opens the packaged app. Use th
## Current Scope
- Native macOS SwiftUI cockpit.
- Liquid-glass-inspired material panels.
- Buttons for cockpit, lifecycle status, automation proposal, and full report.
- Native macOS SwiftUI control room.
- Mission Control with one explicit next action.
- Research Map with the full ratchet pathway.
- Monitor-only automation approval gate.
- Manual exposure recording and closing controls.
- Reports panel for raw artifacts.
- Ratchet Lecture for the general autoresearch method.
- Monitor-only. It never places Braiins orders.
## Product Direction

View file

@ -18,6 +18,7 @@ Commands:
app Build and open the native macOS app.
position Record/list/close manually executed Braiins exposure.
report Print the latest stored report without fetching new data.
app-state Print structured JSON for the native macOS app.
experiments Print the Karpathy-style experiment ledger.
retro SINCE [UNTIL] Write a retroactive report from stored snapshots.
raw-cycle Run one full monitor cycle and print raw JSON.
@ -42,7 +43,7 @@ USAGE
ensure_venv() {
if [[ ! -x "$PYTHON_BIN" ]]; then
echo "Creating local virtual environment at .venv ..."
echo "Creating local virtual environment at .venv ..." >&2
python3 -m venv "$ROOT_DIR/.venv"
fi
}
@ -113,6 +114,10 @@ cmd_report() {
run_python -m braiins_ratchet.cli report
}
cmd_app_state() {
run_python -m braiins_ratchet.cli app-state
}
cmd_pipeline() {
run_python -m braiins_ratchet.cli pipeline "$@"
}
@ -177,6 +182,7 @@ main() {
app|mac-app) cmd_app "$@" ;;
position|positions) cmd_position "$@" ;;
report) cmd_report "$@" ;;
app-state) cmd_app_state "$@" ;;
experiments) cmd_experiments "$@" ;;
retro) cmd_retro "$@" ;;
raw-cycle) cmd_raw_cycle "$@" ;;

View file

@ -1,6 +1,7 @@
from __future__ import annotations
import argparse
from dataclasses import asdict
from datetime import UTC, datetime, timedelta
import json
from pathlib import Path
@ -17,7 +18,7 @@ from .experiments import (
summarize_since,
write_retro_report,
)
from .guidance import build_operator_cockpit
from .guidance import build_operator_cockpit, get_operator_state
from .lifecycle import (
close_manual_position,
open_manual_position,
@ -34,6 +35,7 @@ from .storage import (
init_db,
latest_market_snapshot,
latest_ocean_snapshot,
latest_proposal,
save_market_snapshot,
save_ocean_snapshot,
save_proposal,
@ -157,6 +159,26 @@ def cmd_next(_: argparse.Namespace) -> int:
return 0
def cmd_app_state(_: argparse.Namespace) -> int:
with connect() as conn:
init_db(conn)
operator_state = get_operator_state(conn)
automation_plan = build_automation_plan(conn)
payload = {
"generated_at": datetime.now(UTC).isoformat(timespec="seconds"),
"operator_state": asdict(operator_state),
"automation_plan": asdict(automation_plan),
"cockpit": build_operator_cockpit(conn),
"latest": {
"ocean": _object_dict(latest_ocean_snapshot(conn)),
"market": _object_dict(latest_market_snapshot(conn)),
"proposal": _object_dict(latest_proposal(conn)),
},
}
print(json.dumps(payload, default=str, indent=2))
return 0
def cmd_pipeline(args: argparse.Namespace) -> int:
config = load_config(Path(args.config) if args.config else None)
with connect() as conn:
@ -317,6 +339,12 @@ def _proposal_json(proposal: object) -> str:
return json.dumps(proposal, default=default, indent=2)
def _object_dict(value: object | None) -> dict[str, object] | None:
if value is None:
return None
return dict(value.__dict__) if hasattr(value, "__dict__") else {"value": str(value)}
def _run_one_fresh_cycle(config: object) -> None:
with connect() as conn:
run_cycle(conn, config)
@ -387,6 +415,9 @@ def build_parser() -> argparse.ArgumentParser:
next_step = sub.add_parser("next", help="print exactly what the operator should do next")
next_step.set_defaults(func=cmd_next)
app_state = sub.add_parser("app-state", help="print structured JSON for the native app")
app_state.set_defaults(func=cmd_app_state)
pipeline = sub.add_parser("pipeline", help="propose and confirm the next automation step")
pipeline.add_argument("--config")
pipeline.add_argument("--yes", action="store_true", help="accept the printed plan without prompting")

View file

@ -1,6 +1,8 @@
from pathlib import Path
import unittest
from braiins_ratchet.cli import build_parser
ROOT = Path(__file__).resolve().parents[1]
@ -12,8 +14,14 @@ class MacAppPackagingTest(unittest.TestCase):
self.assertIn("app|mac-app", text)
self.assertIn("cmd_app", text)
self.assertIn("app-state", text)
self.assertNotIn("swift run BraiinsRatchetMac", text)
def test_python_cli_exposes_structured_app_state(self):
args = build_parser().parse_args(["app-state"])
self.assertEqual(args.func.__name__, "cmd_app_state")
def test_mac_app_builder_creates_bundle_contract(self):
builder = ROOT / "scripts" / "build_mac_app"
text = builder.read_text()
@ -36,3 +44,14 @@ class MacAppPackagingTest(unittest.TestCase):
text = path.read_text()
self.assertIn("./scripts/ratchet app", text)
self.assertNotIn("swift run BraiinsRatchetMac", text)
def test_swift_app_uses_native_dashboard_not_raw_terminal_as_primary_ui(self):
source = ROOT / "macos" / "BraiinsRatchet" / "Sources" / "BraiinsRatchetMac" / "BraiinsRatchetApp.swift"
text = source.read_text()
self.assertIn("NavigationSplitView", text)
self.assertIn("MissionControlView", text)
self.assertIn("ResearchTimeline", text)
self.assertIn("AutoresearchOrb", text)
self.assertIn("AppStatePayload", text)
self.assertIn("loadAppState", text)