Compare commits

...

2 Commits

Author SHA1 Message Date
Jacob Schmidt
3789492de3 Refactor DevTool constructors and include WebGateway in solution 2026-02-27 19:52:27 -06:00
Jacob Schmidt
29e027b918 Refactor WebGateway state classes and tuple usage 2026-02-27 19:51:31 -06:00
7 changed files with 54 additions and 88 deletions

View File

@ -5,7 +5,7 @@ using Spectre.Console;
// ── Workspace + project discovery ────────────────────────────────────────────
var workspaceResult = WorkspaceLoader.FindAndLoad();
var projectResult = ConfigLoader.FindAndLoad();
var projectResult = ConfigLoader.FindAndLoad();
if (projectResult is null)
{
@ -17,7 +17,7 @@ if (projectResult is null)
// ── Main run loop (handles workspace project switching) ───────────────────────
var (currentConfig, currentRoot) = projectResult.Value;
var (workspace, workspaceRoot) = workspaceResult.HasValue
var (workspace, workspaceRoot) = workspaceResult.HasValue
? (workspaceResult.Value.Config, workspaceResult.Value.WorkspaceRoot)
: ((WorkspaceConfig?)null, (string?)null);
@ -25,7 +25,7 @@ try
{
while (true)
{
var app = new App(currentConfig, currentRoot, workspace, workspaceRoot);
var app = new App(currentConfig, currentRoot, workspace, workspaceRoot);
var result = await app.RunAsync();
if (result.Reason == AppExitReason.Quit)

View File

@ -10,28 +10,20 @@ internal sealed record MenuItem(string Display, string Value);
public enum AppExitReason { Quit, SwitchProject }
public sealed record AppResult(AppExitReason Reason, string? NewProjectRoot = null);
public sealed class App
public sealed class App(
DevToolConfig config,
string projectRoot,
WorkspaceConfig? workspace = null,
string? workspaceRoot = null)
{
private DevToolConfig _config;
private string _projectRoot;
private readonly WorkspaceConfig? _workspace;
private readonly string? _workspaceRoot;
private DevToolConfig _config = config;
private string _projectRoot = projectRoot;
private readonly WorkspaceConfig? _workspace = workspace;
private readonly string? _workspaceRoot = workspaceRoot;
private IReadOnlyDictionary<string, BuildTarget> TargetMap =>
_config.Targets.ToDictionary(t => t.Id, StringComparer.OrdinalIgnoreCase);
public App(
DevToolConfig config,
string projectRoot,
WorkspaceConfig? workspace = null,
string? workspaceRoot = null)
{
_config = config;
_projectRoot = projectRoot;
_workspace = workspace;
_workspaceRoot = workspaceRoot;
}
public async Task<AppResult> RunAsync()
{
while (true)

View File

@ -10,34 +10,34 @@ namespace Sdt.Tui;
internal static class Theme
{
// ── Hex colour constants (use in Spectre markup strings) ─────────────────
public const string Green = "#00ff41"; // primary phosphor — all normal text
public const string GreenDim = "#005c1b"; // muted — borders, secondary info
public const string GreenBold = "#a8ff90"; // bright — selections, emphasis
public const string Amber = "#ffb300"; // warnings / group titles
public const string Red = "#ff4040"; // errors
public const string Ghost = "#003d12"; // near-invisible — decorative scanlines
public const string Green = "#00ff41"; // primary phosphor — all normal text
public const string GreenDim = "#005c1b"; // muted — borders, secondary info
public const string GreenBold = "#a8ff90"; // bright — selections, emphasis
public const string Amber = "#ffb300"; // warnings / group titles
public const string Red = "#ff4040"; // errors
public const string Ghost = "#003d12"; // near-invisible — decorative scanlines
// ── Spectre Color instances (for FigletText, Rule styles, etc.) ──────────
public static readonly Color GreenColor = new(0, 255, 65);
public static readonly Color GreenDimColor = new(0, 92, 27);
public static readonly Color GreenColor = new(0, 255, 65);
public static readonly Color GreenDimColor = new(0, 92, 27);
public static readonly Color GreenBoldColor = new(168, 255, 144);
public static readonly Color AmberColor = new(255, 179, 0);
public static readonly Color RedColor = new(255, 64, 64);
public static readonly Color AmberColor = new(255, 179, 0);
public static readonly Color RedColor = new(255, 64, 64);
// ── Pre-built Style objects ───────────────────────────────────────────────
public static readonly Style PrimaryStyle = new(GreenColor);
public static readonly Style DimStyle = new(GreenDimColor);
public static readonly Style BrightStyle = new(GreenBoldColor, decoration: Decoration.Bold);
public static readonly Style AmberStyle = new(AmberColor);
public static readonly Style RedStyle = new(RedColor, decoration: Decoration.Bold);
public static readonly Style PrimaryStyle = new(GreenColor);
public static readonly Style DimStyle = new(GreenDimColor);
public static readonly Style BrightStyle = new(GreenBoldColor, decoration: Decoration.Bold);
public static readonly Style AmberStyle = new(AmberColor);
public static readonly Style RedStyle = new(RedColor, decoration: Decoration.Bold);
// ── Markup helper methods (auto-escape user content) ─────────────────────
public static string G(string t) => $"[{Green}]{Markup.Escape(t)}[/]";
public static string G(string t) => $"[{Green}]{Markup.Escape(t)}[/]";
public static string Faint(string t) => $"[{GreenDim}]{Markup.Escape(t)}[/]";
public static string Bold(string t) => $"[bold {GreenBold}]{Markup.Escape(t)}[/]";
public static string Warn(string t) => $"[{Amber}]{Markup.Escape(t)}[/]";
public static string Err(string t) => $"[bold {Red}]{Markup.Escape(t)}[/]";
public static string Ok(string t) => $"[bold {Green}]✓ {Markup.Escape(t)}[/]";
public static string Err(string t) => $"[bold {Red}]{Markup.Escape(t)}[/]";
public static string Ok(string t) => $"[bold {Green}]✓ {Markup.Escape(t)}[/]";
public static string Fail(string t) => $"[bold {Red}]✗ {Markup.Escape(t)}[/]";
// ── Shared UI components ──────────────────────────────────────────────────

View File

@ -5,16 +5,10 @@ using Spectre.Console;
namespace Sdt.Tui;
public sealed class ToolchainScreen
public sealed class ToolchainScreen(DevToolConfig config, string projectRoot)
{
private readonly DevToolConfig _config;
private readonly string _projectRoot;
public ToolchainScreen(DevToolConfig config, string projectRoot)
{
_config = config;
_projectRoot = projectRoot;
}
private readonly DevToolConfig _config = config;
private readonly string _projectRoot = projectRoot;
public async Task RunAsync()
{
@ -83,8 +77,8 @@ public sealed class ToolchainScreen
{
switch (action)
{
case "py:check": await CheckPythonAsync(tc.Python!); break;
case "py:venv": await CreateVenvAsync(tc.Python!); break;
case "py:check": await CheckPythonAsync(tc.Python!); break;
case "py:venv": await CreateVenvAsync(tc.Python!); break;
case "py:install": await InstallProfileAsync(tc.Python!); break;
case "py:upgradepip": await UpgradePipAsync(tc.Python!); break;
case "node:check": await CheckNodeAsync(tc.Node!); break;
@ -208,7 +202,7 @@ public sealed class ToolchainScreen
AnsiConsole.MarkupLine(Theme.Faint($"Post-install: {cmd}"));
var parts = cmd.Split(' ', 2);
var postArgs = parts.Length > 1 ? parts[1].Split(' ') : Array.Empty<string>();
await RunLiveAsync(venvPy, ["-m", ..postArgs], _projectRoot);
await RunLiveAsync(venvPy, ["-m", .. postArgs], _projectRoot);
}
}
@ -263,7 +257,7 @@ public sealed class ToolchainScreen
// ── Helpers ───────────────────────────────────────────────────────────────
private string ResolvePythonExe(PythonToolchain py)
private static string ResolvePythonExe(PythonToolchain py)
{
if (OperatingSystem.IsWindows() && !string.IsNullOrWhiteSpace(py.WindowsExecutable))
return py.WindowsExecutable;

View File

@ -3,18 +3,11 @@ using Spectre.Console;
namespace Sdt.Tui;
public sealed class WorkspaceScreen
public sealed class WorkspaceScreen(WorkspaceConfig workspace, string workspaceRoot, string currentProjectRoot)
{
private readonly WorkspaceConfig _workspace;
private readonly string _workspaceRoot;
private readonly string _currentProjectRoot;
public WorkspaceScreen(WorkspaceConfig workspace, string workspaceRoot, string currentProjectRoot)
{
_workspace = workspace;
_workspaceRoot = workspaceRoot;
_currentProjectRoot = currentProjectRoot;
}
private readonly WorkspaceConfig _workspace = workspace;
private readonly string _workspaceRoot = workspaceRoot;
private readonly string _currentProjectRoot = currentProjectRoot;
/// <summary>
/// Shows the project switcher. Returns the absolute path to the selected project root,

View File

@ -67,11 +67,11 @@ app.MapPost("/api/command", async (CommandEnvelope? command, Entry entry) =>
app.MapGet("/api/sidecar/root", (SidecarRootState rootState) =>
{
var snapshot = rootState.Get();
var (root, isCustom) = rootState.Get();
return Results.Ok(new
{
root = snapshot.Root,
isCustom = snapshot.IsCustom
root,
isCustom
});
});
@ -84,12 +84,12 @@ app.MapPost("/api/sidecar/root", (SetSidecarRootRequest? request, SidecarRootSta
}
rootState.Set(path);
var snapshot = rootState.Get();
Environment.SetEnvironmentVariable("JOURNAL_PROJECT_ROOT", snapshot.Root);
var (root, isCustom) = rootState.Get();
Environment.SetEnvironmentVariable("JOURNAL_PROJECT_ROOT", root);
return Results.Ok(new
{
root = snapshot.Root,
isCustom = snapshot.IsCustom
root,
isCustom
});
});
@ -207,32 +207,20 @@ string ResolveWebDist(string repoRootPath)
string ErrorResponse(string message)
=> JsonSerializer.Serialize(new { ok = false, error = message }, gatewayJsonOptions);
sealed class WebUiState
sealed class WebUiState(string distPath)
{
public WebUiState(string distPath)
{
DistPath = distPath;
}
public string DistPath { get; }
public string DistPath { get; } = distPath;
public bool Exists => Directory.Exists(DistPath) && File.Exists(Path.Combine(DistPath, "index.html"));
}
sealed class SidecarRootState
sealed class SidecarRootState(string autoRoot)
{
private readonly object _sync = new();
private readonly string _autoRoot;
private string _currentRoot;
private readonly string _autoRoot = autoRoot;
private string _currentRoot = autoRoot;
private bool _isCustom;
public SidecarRootState(string autoRoot)
{
_autoRoot = autoRoot;
_currentRoot = autoRoot;
_isCustom = false;
}
public (string Root, bool IsCustom) Get()
{
lock (_sync)
@ -272,5 +260,3 @@ sealed class CommandEnvelope
public string? Tag { get; set; }
public JsonElement? Payload { get; set; }
}

View File

@ -3,4 +3,5 @@
<Project Path="Journal.Sidecar/Journal.Sidecar.csproj" />
<Project Path="Journal.SmokeTests/Journal.SmokeTests.csproj" />
<Project Path="Journal.DevTool/Journal.DevTool.csproj" />
<Project Path="Journal.WebGateway/Journal.WebGateway.csproj" />
</Solution>