Atlas renders an Uno Platform app's full navigation structure as an interactive canvas, and exposes
that same structure as one document (AppModel) that a developer and an AI agent both read.
The thesis: an agent shouldn't have to understand an app only through scattered files. Atlas gives it — and you — the same view: screens, routes, and how you move between them, with every edge marked by where the evidence came from:
- Declared — written in the route map / on a button (static, from the code).
- Observed — actually fired while the app ran (runtime).
- Unreachable — declared but its guard never fires (a dead door).
Code stays the source of truth. The canvas is a projection of AppModel, never the other way around.
New here? Read this file, then
docs/SPEC.md(the build spec), thendocs/CLAUDE.md(conventions). The visual target isdesign/atlas-prototype.html.
Everything is a projection of one immutable record, AppModel (in Atlas.Core/AppModel.cs):
public sealed record AppModel(
string App, DateTimeOffset GeneratedAt,
ModelSource Source, // Static | Runtime | Merged
string SchemaVersion,
IReadOnlyList<AppNode> Nodes, // a screen: id, name, route, view, view-model, status, position…
IReadOnlyList<AppEdge> Edges); // a hop: from, to, Kind (provenance), Trigger, IsDefault, DependsOn
public enum EdgeKind { Declared, Observed, Unreachable }Two independent pipelines fill that document, and the viewer renders the merge:
┌── STATIC (no app run) ─────────────────────────────────────────────────┐
│ App.xaml.cs + *.xaml + *.cs │
│ │ Roslyn │
│ ├─ RouteExtractor → route tree (shell→child, IsDefault, DependsOn)│
│ ├─ TriggerExtractor → lateral flow edges (Navigation.Request + │
│ │ NavigateRouteAsync/ViewModelAsync), labelled │
│ └─ TreeLayout → deterministic node positions │
│ │ │
│ atlas CLI ───────────────► AppModel JSON ──────────┐ │
└────────────────────────────────────────────────────────────┼───────────┘
▼
Atlas.App (the viewer)
▲
┌── RUNTIME (app running) ──────────────────────────────────┼───────────┐
│ Your app + Atlas.Agent ── IRouteNotifier ── NDJSON :9743 ─┘ │
│ → RuntimeBridge merges observed/live deltas into the model │
└────────────────────────────────────────────────────────────────────────┘
- Static extraction turns source into an
AppModelwithout running anything.RouteExtractorparsesRegisterRoutes;TriggerExtractorrecovers the real page→page flow from XAMLNavigation.RequestandNavigate*Asynccall sites;TreeLayoutplaces the nodes. TheatlasCLI is the host that emits JSON. - Runtime feed is optional. A target app references the tiny
Atlas.Agentpackage; it subscribes to Uno'sIRouteNotifierand pushes each navigation over a local NDJSON socket.Atlas.Runtime'sModelMergerflips declared edges to observed as they fire and marks the live screen. - The viewer (
Atlas.App) is an Uno MVUX app. The canvas (ZoomContentControl+EdgeLayer+NodeCard) renders the model; the agent panel runs structural queries (GraphQueries) and a free-text question router (QuestionInterpreter) over the same document.
| Project | TFM | What it holds |
|---|---|---|
Atlas.Core |
netstandard2.0 |
The AppModel contract + JSON, and all pure logic: GraphQueries, SuggestionEngine, QuestionInterpreter, TreeLayout, EditScope. No Uno deps. |
Atlas.Extraction |
net10.0 + Roslyn |
RouteExtractor and TriggerExtractor — source → AppModel. |
Atlas.Runtime |
netstandard2.0 |
Runtime route receiver + ModelMerger (observed/live merge). |
Atlas.Agent |
net10.0 (NuGet) |
Drop-in package for a target app: subscribes IRouteNotifier, pushes events. |
Atlas.Cli |
net10.0 (exe atlas) |
atlas extract — runs extraction + layout, emits JSON. |
Atlas.App |
net10.0-desktop;net10.0-browserwasm |
The Uno viewer: MapPage, NodeCard, EdgeLayer, drawer panels, MVUX models. |
Atlas.Tests |
net10.0 (xunit) |
Extraction, model round-trip, graph-query, interpreter coverage. |
- .NET 10 SDK (see
global.jsonfor the pinnedUno.Sdk). - Windows is the primary dev target; the viewer also runs on macOS/Linux/WASM via the Skia renderer.
- First-time Uno setup:
dotnet tool install -g uno.check && uno-check.
dotnet build Atlas.sln
dotnet test Atlas.Tests/Atlas.Tests.csproj # 64 testsdotnet run -f net10.0-desktop --project .\Atlas.App\Atlas.App.csprojIt boots into a bundled sample. Use Open model… in the top bar (or drag a model .json onto the
canvas, or pass a path as a launch arg) to load any exported model. Hand-arranged node positions
persist per app under %LocalAppData%\Atlas\layouts.
dotnet run --project .\Atlas.Cli\Atlas.Cli.csproj -- `
extract .\samples\RoundsApp\RoundsApp\App.xaml.cs `
--app "RoundsApp" `
--source .\samples\RoundsApp\RoundsApp `
--out rounds.jsonatlas extract <App.xaml.cs> [--app <name>] [--source <dir>] [--out <file>] [--no-layout] [--compact]
--app <name> name stamped into the model (default: the source's project folder)
--source <dir> also scan this project dir for navigation triggers → lateral flow edges
--out <file> write JSON to a file (default: stdout)
--no-layout skip the deterministic tree layout (leave positions null)
--compact emit single-line JSON instead of indented
Without --source, you get just the route tree. With it, the lateral flow edges (with trigger
labels like "Start rounding") are layered on top. Open the resulting JSON in the viewer.
Add the agent to your Uno app in Debug only and start it once after the host is built:
Host = await builder.NavigateAsync<Shell>();
#if DEBUG
_ = Atlas.Agent.AtlasAgent.Start(Host.Services, "MyApp");
#endifRun the viewer and your app together; declared edges flip to observed as you navigate. Requires
Uno.Extensions Navigation (IRouteNotifier). Full reference integration: samples/RoundsApp.
See TOOLING.md for packaging and publishing the viewer as a standalone tool.
AppModelis the center of gravity. The canvas, the agent, and the runtime feed all read it. Change the contract deliberately and update the fixture (samples/rounds-app-model.json) + tests together.- Keep logic pure and in
Atlas.Core.GraphQueries/SuggestionEngine/QuestionInterpreter/TreeLayoutare pure functions with xunit coverage. New analysis goes there with a test, not in the viewer. - The viewer is MVUX. Models are
partial records exposingIFeed/IState/IListFeed; public async methods become commands. Bind with{Binding}(the MVUX proxy needs it —x:Bindbypasses it). - Styling: no hardcoded hex colors, no inline
FontSize, no{Binding StringFormat}. Use the Material brush/typography resources and theAtlas*keys inThemes/. - Uno-specific work: initialize the Uno MCP rules and consult the matching skill before
writing navigation/MVUX/Toolkit/styling code (per
docs/CLAUDE.md).
Small steps; after each meaningful change run dotnet build / dotnet test, then commit with a
conventional message (feat:, fix:, refactor:, perf:, docs:, test:). Stop the running
viewer before a rebuild to avoid file locks.
- A new extraction shape (another navigation pattern) →
Atlas.Extraction, with a synthetic test and, where possible, a real-source integration test using asamples/fixture. - A new structural query / agent answer →
Atlas.Core/GraphQueries.cs(pure + tested), then surface it viaSuggestionEngine(a chip) and/orQuestionInterpreter(a typed-question route). - Canvas / panel UI →
Atlas.App/Presentation(MVUX models + XAML; custom controls underControls/).
Phases 0–4 + tooling are in. Phase 3 (static extraction) is complete: route tree + DependsOn +
XAML/code trigger inference, hosted by the atlas CLI. Known best-effort gaps (tracked in docs/SPEC.md):
qualifier+route combos (-/Home), region-by-selection nav with no Navigation.Request, and
non-Extensions/Frame navigation. The agent question box uses a local router today; wiring the Claude
API for true natural language is the open Phase-4 upgrade.
| File | Read it for |
|---|---|
docs/SPEC.md |
The build spec — architecture, phased plan, open questions. |
docs/CLAUDE.md |
Conventions, commands, stop conditions, the Uno-MCP rule. |
TOOLING.md |
Running/publishing the viewer and wiring the agent into a target app. |
design/atlas-prototype.html |
The interactive visual reference. |
samples/ |
The canonical AppModel fixtures and the RoundsApp reference app. |