Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .repository-projection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"projection": "deixic-code",
"projectionSchemaVersion": 1,
"sourceRepository": "dx-corp/mono",
"sourceSha": "6139cda76fc6bd4d07a40c8351468364c090e619",
"sourceSha": "915d91afd3e2038786a2e1cf12dd920ce17a8316",
"destinationRepository": "dx-corp/code",
"priorProjectedBase": "7f514fca918d431635a0ba92a825eb4bd0812cac",
"definitionDigest": "064b6b9745d392dde277afd7dfdf028cff32b6178a8b9c99c518343219bab429",
"toolDigest": "c8000985e1bd708bf1f8754b27cc1a2aad67af10ee23cf5317bd1fba670b4456",
"contentDigest": "0a9c99f245ca6f3b5929664ecdaa6dc22347566ee2a0af6f97bd8ac4c482bc64",
"priorProjectedBase": "09d57e69e8649768da349fb38eb9c513df0e5d01",
"definitionDigest": "cc5446d8982464d5504de96b74641fe6f74b8e430830d0cca507e19fab0041b8",
"toolDigest": "de718162d050963e0c04ca9cbc0e38b1db545e838e587f949bcd9fd8aded6bcd",
"contentDigest": "eeeb25196b828d9c832f1193fd3974f52c00fd422ed308c137262393d8a9b7d4",
"publicationEligible": true
}
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ cargo run -p maestro -- web --port 3000

Use `cargo test -p <package> [test-name]` for focused work. Workspace package names include `maestro`, `maestro-tui`, `maestro-runtime`, `maestro-runtime-contracts`, `maestro-runtime-gateway`, and `ambient-agent`.

### Native UI development

Use the repository-local UI front door instead of remembering Cargo examples
or fixture paths:

```bash
./dev ui # live full-flow workbench
./dev ui list # registered stories and cases
./dev ui inspect menu-ready # owner, source, states, and exact commands
./dev ui check menu-ready # focused render and behavior verification
./dev ui new workspace-picker --adapter shared-menu --check
./dev ui doctor
```

`./dev ui review --story ID` keeps the familiar AI composer and onboarding
flows in the production Rust renderer while narrowing rebuilds to the selected
story. Exported menu and theme receipts replay with `./dev ui replay FILE`.
Run `./dev ui migrate FILE --check` on checked-in receipts. See the
[UI contributor workflow](docs/ui-contributor-workflow.md) for adapters,
compatibility, and pull request evidence.
Comment on lines +44 to +45

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Link to a workflow that exists in the public repository

The newly added contributor link targets docs/ui-contributor-workflow.md, but that file is absent from this commit and a repo-wide search finds only this reference. Public contributors following the new UI setup therefore reach a broken link; add the projected workflow document or point this link at an existing public guide.

Useful? React with 👍 / 👎.


## Verification

Before opening a pull request, run the checks relevant to the changed surface. For workspace or release changes, run the full set:
Expand Down
214 changes: 214 additions & 0 deletions dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""Stable contributor commands for Deixic Code."""

from __future__ import annotations

import argparse
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys


MAESTRO_ROOT = Path(__file__).resolve().parent
MONO_ROOT = (
MAESTRO_ROOT.parents[1]
if MAESTRO_ROOT.parent.name == "products"
else MAESTRO_ROOT
)
WORKBENCH = MAESTRO_ROOT / "scripts/dev/ui-workbench.py"


def cargo_preview(*args: str, stdin: bytes | None = None) -> int:
command = [
"cargo",
"run",
"--quiet",
"--locked",
"-p",
"maestro-ui-preview",
"--",
*args,
]
return subprocess.run(command, cwd=MAESTRO_ROOT, input=stdin).returncode


def cargo_onboarding(mode: str, body: bytes) -> int:
command = [
"cargo",
"run",
"--quiet",
"--locked",
"-p",
"maestro-tui",
"--example",
"onboarding-preview",
"--",
mode,
]
return subprocess.run(command, cwd=MAESTRO_ROOT, input=body).returncode


def read_receipt(path: str) -> tuple[bytes, str]:
receipt = Path(path)
if receipt.stat().st_size > 1_048_576:
raise ValueError("receipt exceeds 1048576 bytes")
body = receipt.read_bytes()
value = json.loads(body)
if not isinstance(value, dict) or not isinstance(value.get("schema"), str):
raise ValueError("receipt must be a versioned wire envelope")
return body, value["schema"]


def changed_review_filter() -> list[str]:
if MONO_ROOT == MAESTRO_ROOT or not (MONO_ROOT / ".git").exists():
return []
base = subprocess.run(
["git", "merge-base", "origin/main", "HEAD"],
cwd=MONO_ROOT,
capture_output=True,
text=True,
check=True,
).stdout.strip()
paths = subprocess.run(
["git", "diff", "--name-only", base, "HEAD", "--", "products/maestro"],
cwd=MONO_ROOT,
capture_output=True,
text=True,
check=True,
).stdout.splitlines()
visual = [path for path in paths if "/packages/ui-preview-rs/" in path]
if visual and len(visual) == len(paths):
return ["--components-only", "--adapter", "shared-menu"]
return []


def doctor(as_json: bool) -> int:
tools = {name: shutil.which(name) for name in ("cargo", "python3", "rustfmt")}
required_paths = [
MAESTRO_ROOT / "Cargo.toml",
MAESTRO_ROOT / "packages/ui-preview-rs/Cargo.toml",
WORKBENCH,
]
missing = [str(path) for path in required_paths if not path.is_file()]
ready = tools["cargo"] is not None and tools["python3"] is not None and not missing
report = {
"status": "ready" if ready else "blocked",
"maestro_root": str(MAESTRO_ROOT),
"repository_root": str(MONO_ROOT),
"layout": "mono" if MONO_ROOT != MAESTRO_ROOT else "public",
"tools": tools,
"missing": missing,
}
if as_json:
print(json.dumps(report, indent=2, sort_keys=True))
else:
print(f"Maestro UI development: {report['status']}")
print(f"Layout: {report['layout']} ({MAESTRO_ROOT})")
for name, path in tools.items():
print(f"{name}: {path or 'missing'}")
for path in missing:
print(f"missing: {path}")
return 0 if ready else 2


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="./dev", description=__doc__)
top = parser.add_subparsers(dest="area", required=True)
ui = top.add_parser("ui", help="design, inspect, replay, and review native UI stories")
commands = ui.add_subparsers(dest="command")

commands.add_parser("list", help="list registered stories and cases")
new = commands.add_parser("new", help="plan or create an adapter-owned story")
new.add_argument("story")
new.add_argument("--adapter", default="shared-menu")
new.add_argument("--check", action="store_true")

check = commands.add_parser("check", help="render one story and evaluate its contract")
check.add_argument("story")
check.add_argument("--require-contract", action="store_true")

inspect = commands.add_parser("inspect", help="show owner, source, cases, and exact commands")
inspect.add_argument("story")
inspect.add_argument("--json", action="store_true", help="reserved; output is always JSON")

replay = commands.add_parser("replay", help="replay a portable menu or theme receipt")
replay.add_argument("receipt")

migrate = commands.add_parser("migrate", help="migrate a versioned recipe, replay, or contract")
migrate.add_argument("receipt")
migrate.add_argument("--check", action="store_true")

review = commands.add_parser("review", help="open the live production-renderer workbench")
review.add_argument("--story")
review.add_argument("--adapter")
review.add_argument("--components-only", action="store_true")
review.add_argument("--changed", action="store_true")
review.add_argument("--port", type=int, default=8770)

doctor_parser = commands.add_parser("doctor", help="check the local UI toolchain and layout")
doctor_parser.add_argument("--json", action="store_true")
return parser


def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if args.area != "ui":
parser.error("unknown development area")
command = args.command or "review"
if command == "list":
return cargo_preview("--list")
if command == "new":
tail = ["studio", "new", "--adapter", args.adapter, args.story]
if args.check:
tail.append("--check")
return cargo_preview(*tail)
if command == "check":
tail = ["studio", "verify", args.story]
if args.require_contract:
tail.append("--require-contract")
return cargo_preview(*tail)
if command == "inspect":
return cargo_preview("studio", "inspect", args.story)
if command == "migrate":
tail = ["studio", "migrate", args.receipt]
if args.check:
tail.append("--check")
return cargo_preview(*tail)
if command == "replay":
try:
body, schema = read_receipt(args.receipt)
except (OSError, ValueError, json.JSONDecodeError) as error:
print(error, file=sys.stderr)
return 2
if schema == "maestro.ui.menu-recipe":
return cargo_onboarding("--studio-stdin", body)
if schema == "maestro.ui.theme-replay":
return cargo_onboarding("--replay-stdin", body)
print(f"unsupported replay schema: {schema}", file=sys.stderr)
return 2
if command == "doctor":
return doctor(args.json)
if command == "review":
port = getattr(args, "port", 8770)
story = getattr(args, "story", None)
adapter = getattr(args, "adapter", None)
tail = [sys.executable, str(WORKBENCH), "--port", str(port)]
if getattr(args, "changed", False):
tail.extend(changed_review_filter())
Comment on lines +200 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent --changed from adding a second scope filter

In a Mono checkout where changed_review_filter() selects --adapter shared-menu, invoking ./dev ui review --changed --story menu-ready appends both adapter and story filters. The downstream parser defines these as mutually exclusive in scripts/dev/ui-workbench.py:730-732, so this otherwise accepted front-door command exits before opening the workbench. Make --changed mutually exclusive with explicit selectors, or let the explicit selector override the inferred filter.

Useful? React with 👍 / 👎.

elif getattr(args, "components_only", False):
tail.append("--components-only")
if story:
tail.extend(["--story", story])
if adapter:
tail.extend(["--adapter", adapter])
return subprocess.run(tail, cwd=MAESTRO_ROOT).returncode
parser.error(f"unknown ui command: {command}")
return 2


if __name__ == "__main__":
raise SystemExit(main())
59 changes: 47 additions & 12 deletions packages/tui-rs/examples/onboarding-preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,21 @@ fn fixture(id: &str, expected: SetupPage) -> SetupModal {
assert_eq!(modal.page(), expected, "fixture {id}");
modal
}
fn captures() -> Result<Vec<Capture>, String> {
fn captures_selected(story: Option<&str>) -> Result<Vec<Capture>, String> {
let mut captures = Vec::new();
for (width, height) in [(40, 24), (60, 32), (100, 40)] {
for motion in [true, false] {
let scene_id = if motion {
"first-boot"
} else {
"first-boot-motion-off"
};
if story.is_some_and(|selected| selected != scene_id) {
continue;
}
for tick in 0..if motion { 36 } else { 1 } {
let scene = Scene {
id: if motion {
"first-boot"
} else {
"first-boot-motion-off"
}
.into(),
id: scene_id.into(),
label: if motion {
"First launch"
} else {
Expand All @@ -141,6 +144,10 @@ fn captures() -> Result<Vec<Capture>, String> {
}
}
for &(id, page) in STATES {
let scene_id = format!("onboarding-{id}");
if story.is_some_and(|selected| selected != scene_id) {
continue;
}
let times: &[u64] = if id == "welcome" {
&[0, 640, 1280, 1920]
} else {
Expand All @@ -149,7 +156,7 @@ fn captures() -> Result<Vec<Capture>, String> {
for &time_ms in times {
let mut modal = fixture(id, page);
let scene = Scene {
id: format!("onboarding-{id}"),
id: scene_id.clone(),
label: format!("Onboarding / {id}"),
width,
height,
Expand All @@ -172,10 +179,22 @@ fn captures() -> Result<Vec<Capture>, String> {
for capture in &mut captures {
capture.source = "products/maestro/packages/tui-rs/examples/onboarding-preview.rs".into();
}
captures.extend(support::ui_stories::captures()?);
captures.extend(maestro_ui_preview::registry()?.captures()?);
captures.extend(support::ui_stories::captures_selected(story)?);
match story {
Some(id) => {
if let Ok(selected) = maestro_ui_preview::registry()?
.filtered(maestro_ui_preview::StoryFilter::Story(id.into()))
{
captures.extend(selected.captures()?);
}
}
None => captures.extend(maestro_ui_preview::registry()?.captures()?),
}
Ok(captures)
}
fn captures() -> Result<Vec<Capture>, String> {
captures_selected(None)
}
fn profiled_captures(profile_id: &str) -> Result<Vec<Capture>, String> {
let profile = maestro_ui_preview::schema::builtin_profile(profile_id)?;
let mut selected = captures()?;
Expand Down Expand Up @@ -272,6 +291,7 @@ fn run() -> Result<(), String> {
}
let mut format = None;
let mut profile = None;
let mut story = None;
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
Expand All @@ -284,13 +304,28 @@ fn run() -> Result<(), String> {
.as_str(),
);
}
"--story" if story.is_none() => {
index += 1;
story = Some(args.get(index).ok_or("missing value for --story")?.as_str());
}
_ => {
return Err("usage: onboarding-preview [--html|--json] [--profile pr-v1|scheduled-v1] | [--sequences|--replay-stdin|--studio-stdin]".into());
return Err("usage: onboarding-preview [--html|--json] [--profile pr-v1|scheduled-v1] [--story ID] | [--sequences|--replay-stdin|--studio-stdin]".into());
}
}
index += 1;
}
let captures = profile.map_or_else(captures, profiled_captures)?;
if profile.is_some() && story.is_some() {
return Err("--profile and --story cannot be combined".into());
}
let captures = if let Some(story) = story {
let selected = captures_selected(Some(story))?;
if selected.is_empty() {
return Err(format!("unknown story: {story}"));
}
selected
} else {
profile.map_or_else(captures, profiled_captures)?
};
let output = if format == Some("--html") {
review::html(&captures)?
} else {
Expand Down
Loading
Loading