some gui implementations and fixed a gitea issue.
This commit is contained in:
parent
2c5493f249
commit
6f4ddecf44
65
.github/workflows/reliability-matrix.yml
vendored
65
.github/workflows/reliability-matrix.yml
vendored
@ -1,65 +0,0 @@
|
|||||||
name: reliability-matrix
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
matrix:
|
|
||||||
name: ${{ matrix.os }} / .NET tests
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup .NET
|
|
||||||
uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
dotnet-version: 10.0.x
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Restore
|
|
||||||
run: dotnet restore DevTool.csproj
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: dotnet build DevTool.csproj -c Release --no-restore
|
|
||||||
|
|
||||||
- name: Verify workflow routes (static)
|
|
||||||
run: python scripts/verify-workflow-routes.py --project-root .
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: dotnet test tests/DevTool.Tests/DevTool.Tests.csproj -c Release --no-build --logger "trx;LogFileName=test-results.trx"
|
|
||||||
|
|
||||||
- name: Generate matrix summary
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
New-Item -ItemType Directory -Force -Path artifacts | Out-Null
|
|
||||||
@"
|
|
||||||
{
|
|
||||||
"os": "${{ matrix.os }}",
|
|
||||||
"commit": "${{ github.sha }}",
|
|
||||||
"workflow": "${{ github.workflow }}",
|
|
||||||
"runId": "${{ github.run_id }}",
|
|
||||||
"runNumber": "${{ github.run_number }}",
|
|
||||||
"status": "passed"
|
|
||||||
}
|
|
||||||
"@ | Set-Content artifacts/reliability-${{ matrix.os }}.json -Encoding UTF8
|
|
||||||
|
|
||||||
- name: Upload test artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: reliability-${{ matrix.os }}
|
|
||||||
path: |
|
|
||||||
**/test-results.trx
|
|
||||||
artifacts/reliability-${{ matrix.os }}.json
|
|
||||||
13
ROADMAP.md
13
ROADMAP.md
@ -52,13 +52,24 @@
|
|||||||
- [x] GUI parity phase 1 bridge foundation shipped (`sdt bridge --stdio` + `DevTool.Host.Bridge`)
|
- [x] GUI parity phase 1 bridge foundation shipped (`sdt bridge --stdio` + `DevTool.Host.Bridge`)
|
||||||
- [x] GUI debug + failure UX slice shipped (debug run, failure card, run context/lifecycle panel)
|
- [x] GUI debug + failure UX slice shipped (debug run, failure card, run context/lifecycle panel)
|
||||||
- [x] GUI history/events/env/doctor/setup-plan read views shipped via bridge methods
|
- [x] GUI history/events/env/doctor/setup-plan read views shipped via bridge methods
|
||||||
- [ ] GUI workspace/favorites polish (switching UX + quick action ergonomics) still in progress
|
- [x] GUI workspace project switcher shipped (configured projects -> set active context)
|
||||||
|
- [x] GUI favorite quick action runner shipped (run favorite directly from workspace panel)
|
||||||
|
- [x] GUI workspace/favorites search/filter/sort ergonomics shipped
|
||||||
|
- [x] GUI "Switch + Run" one-click action shipped from configured projects list
|
||||||
|
- [x] GUI view preferences persisted (workspace-scoped filters/sorts/context defaults)
|
||||||
|
- [x] GUI workspace/favorites advanced grouping shipped (tool/path + project/workflow views)
|
||||||
|
- [x] GUI workspace/favorites bulk actions shipped (bulk run filtered projects/favorites, bulk remove filtered favorites)
|
||||||
|
- [x] GUI setup actions shipped (apply autofix dirs, apply legacy migration, apply recommended config with backup)
|
||||||
|
- [x] GUI keyboard refinements shipped (`Ctrl+K` command palette, `?` shortcut help)
|
||||||
|
- [x] GUI env profile editor shipped (select/load, set-active, create/update profile values)
|
||||||
- [x] Create dedicated `src/DevTool.Host.Gui/TauriShell` scaffold to keep GUI work isolated from TUI/core
|
- [x] Create dedicated `src/DevTool.Host.Gui/TauriShell` scaffold to keep GUI work isolated from TUI/core
|
||||||
- [x] Bootstrap first Tauri command bridge: `sdt workspace scan --json`
|
- [x] Bootstrap first Tauri command bridge: `sdt workspace scan --json`
|
||||||
- [x] Structural refactor to domain-separated projects under `src/` (`DevTool.Engine`, `DevTool.Runtime`, `DevTool.Host.Tui`)
|
- [x] Structural refactor to domain-separated projects under `src/` (`DevTool.Engine`, `DevTool.Runtime`, `DevTool.Host.Tui`)
|
||||||
- [x] Add second Tauri bridge command: `sdt run <workflowId> --json` with live stream panel
|
- [x] Add second Tauri bridge command: `sdt run <workflowId> --json` with live stream panel
|
||||||
- [ ] Remove legacy PowerShell wrappers in v2
|
- [ ] Remove legacy PowerShell wrappers in v2
|
||||||
- [x] Add workspace project inventory model (all `.slnx/.sln/.csproj`) for GUI/TUI multi-project selector
|
- [x] Add workspace project inventory model (all `.slnx/.sln/.csproj`) for GUI/TUI multi-project selector
|
||||||
|
- [x] Expand GUI command palette coverage across workspace/run/setup/history/events/favorites actions
|
||||||
|
- [x] Add full GUI env var definition editor parity (`env[]` model editing with validation)
|
||||||
|
|
||||||
## Next Sprint (v1.3 UX Foundation)
|
## Next Sprint (v1.3 UX Foundation)
|
||||||
|
|
||||||
|
|||||||
@ -36,13 +36,21 @@ Error response:
|
|||||||
- `workspace.add` (`candidatePath`, `initializeConfig`)
|
- `workspace.add` (`candidatePath`, `initializeConfig`)
|
||||||
- `favorites.list`
|
- `favorites.list`
|
||||||
- `favorites.toggle` (`favoriteProjectPath`, `workflowId`, `label`)
|
- `favorites.toggle` (`favoriteProjectPath`, `workflowId`, `label`)
|
||||||
|
- `favorites.removeMany` (`items[]` with `projectPath`, `workflowId`)
|
||||||
- `history.list` (`limit`)
|
- `history.list` (`limit`)
|
||||||
- `events.listFiles`
|
- `events.listFiles`
|
||||||
- `events.readFile` (`filePath`)
|
- `events.readFile` (`filePath`)
|
||||||
- `envProfiles.list`
|
- `envProfiles.list`
|
||||||
- `envProfiles.resolve` (`envProfile`)
|
- `envProfiles.resolve` (`envProfile`)
|
||||||
|
- `envProfiles.setActive` (`profileId`)
|
||||||
|
- `envProfiles.saveProfile` (`profile` object with `id`, `description`, `inherits[]`, `values{}`)
|
||||||
|
- `envVars.list`
|
||||||
|
- `envVars.save` (`env[]` array of env var definitions)
|
||||||
- `doctor.run`
|
- `doctor.run`
|
||||||
- `setup.plan` (read-only preview)
|
- `setup.plan` (read-only preview)
|
||||||
|
- `setup.autofixDirs` (apply missing directory fixes)
|
||||||
|
- `setup.migrateLegacy` (apply targets->workflows migration)
|
||||||
|
- `setup.applyRecommendedConfig` (apply recommended config updates with backup)
|
||||||
|
|
||||||
## Determinism Notes
|
## Determinism Notes
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"updatedAtUtc": "2026-03-02T01:00:00Z",
|
"updatedAtUtc": "2026-03-02T02:00:00Z",
|
||||||
"features": [
|
"features": [
|
||||||
{
|
{
|
||||||
"id": "workspace.switch_and_candidates",
|
"id": "workspace.switch_and_candidates",
|
||||||
"tui": true,
|
"tui": true,
|
||||||
"gui": true,
|
"gui": true,
|
||||||
"status": "in_progress",
|
"status": "done",
|
||||||
"owner": "bridge",
|
"owner": "bridge",
|
||||||
"notes": "GUI can load configured + candidate projects and add/add+init candidates through workspace bridge."
|
"notes": "GUI can load configured + candidate projects, add/add+init candidates, switch context, group/filter, and bulk run filtered projects."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "workflow.run",
|
"id": "workflow.run",
|
||||||
@ -54,17 +54,33 @@
|
|||||||
"id": "favorites.quick_actions",
|
"id": "favorites.quick_actions",
|
||||||
"tui": true,
|
"tui": true,
|
||||||
"gui": true,
|
"gui": true,
|
||||||
"status": "in_progress",
|
"status": "done",
|
||||||
"owner": "bridge",
|
"owner": "bridge",
|
||||||
"notes": "Bridge exposes favorites list/toggle; richer quick-action UI still pending."
|
"notes": "GUI supports favorites run/toggle, grouping/filtering, bulk run filtered favorites, and bulk remove via bridge."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "setup_wizard_autofix",
|
"id": "setup_wizard_autofix",
|
||||||
"tui": true,
|
"tui": true,
|
||||||
"gui": false,
|
"gui": true,
|
||||||
"status": "planned",
|
"status": "in_progress",
|
||||||
"owner": "gui",
|
"owner": "gui",
|
||||||
"notes": "GUI currently exposes doctor + setup plan preview only."
|
"notes": "GUI now supports doctor/setup plan plus apply flows for dir autofix, legacy migration, and recommended config updates."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "env_management_profile_editor",
|
||||||
|
"tui": true,
|
||||||
|
"gui": true,
|
||||||
|
"status": "done",
|
||||||
|
"owner": "gui",
|
||||||
|
"notes": "GUI supports env profile selector/load, set-active, create/update profile values, and full env[] definition editing with validation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "command_palette_and_shortcuts",
|
||||||
|
"tui": true,
|
||||||
|
"gui": true,
|
||||||
|
"status": "done",
|
||||||
|
"owner": "gui",
|
||||||
|
"notes": "GUI provides Ctrl+K command palette and ? shortcut help, including dynamic workspace switch and favorites run commands plus setup/doctor/history/events actions."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,13 +74,21 @@ public sealed class BridgeStdioServer
|
|||||||
"workspace.add" => Ok(request.Id, HandleWorkspaceAdd(request.Params)),
|
"workspace.add" => Ok(request.Id, HandleWorkspaceAdd(request.Params)),
|
||||||
"favorites.list" => Ok(request.Id, HandleFavoritesList(request.Params)),
|
"favorites.list" => Ok(request.Id, HandleFavoritesList(request.Params)),
|
||||||
"favorites.toggle" => Ok(request.Id, HandleFavoritesToggle(request.Params)),
|
"favorites.toggle" => Ok(request.Id, HandleFavoritesToggle(request.Params)),
|
||||||
|
"favorites.removeMany" => Ok(request.Id, HandleFavoritesRemoveMany(request.Params)),
|
||||||
"history.list" => Ok(request.Id, HandleHistoryList(request.Params)),
|
"history.list" => Ok(request.Id, HandleHistoryList(request.Params)),
|
||||||
"events.listFiles" => Ok(request.Id, HandleEventsListFiles(request.Params)),
|
"events.listFiles" => Ok(request.Id, HandleEventsListFiles(request.Params)),
|
||||||
"events.readFile" => Ok(request.Id, HandleEventsReadFile(request.Params)),
|
"events.readFile" => Ok(request.Id, HandleEventsReadFile(request.Params)),
|
||||||
"envProfiles.list" => Ok(request.Id, HandleEnvProfilesList(request.Params)),
|
"envProfiles.list" => Ok(request.Id, HandleEnvProfilesList(request.Params)),
|
||||||
"envProfiles.resolve" => Ok(request.Id, HandleEnvProfilesResolve(request.Params)),
|
"envProfiles.resolve" => Ok(request.Id, HandleEnvProfilesResolve(request.Params)),
|
||||||
|
"envProfiles.setActive" => Ok(request.Id, HandleEnvProfilesSetActive(request.Params)),
|
||||||
|
"envProfiles.saveProfile" => Ok(request.Id, HandleEnvProfilesSaveProfile(request.Params)),
|
||||||
|
"envVars.list" => Ok(request.Id, HandleEnvVarsList(request.Params)),
|
||||||
|
"envVars.save" => Ok(request.Id, HandleEnvVarsSave(request.Params)),
|
||||||
"doctor.run" => Ok(request.Id, await HandleDoctorRunAsync(request.Params, cancellationToken).ConfigureAwait(false)),
|
"doctor.run" => Ok(request.Id, await HandleDoctorRunAsync(request.Params, cancellationToken).ConfigureAwait(false)),
|
||||||
"setup.plan" => Ok(request.Id, HandleSetupPlan(request.Params)),
|
"setup.plan" => Ok(request.Id, HandleSetupPlan(request.Params)),
|
||||||
|
"setup.autofixDirs" => Ok(request.Id, HandleSetupAutofixDirs(request.Params)),
|
||||||
|
"setup.migrateLegacy" => Ok(request.Id, HandleSetupMigrateLegacy(request.Params)),
|
||||||
|
"setup.applyRecommendedConfig" => Ok(request.Id, HandleSetupApplyRecommendedConfig(request.Params)),
|
||||||
_ => Error(request.Id, "method_not_found", $"Unsupported bridge method: {request.Method}")
|
_ => Error(request.Id, "method_not_found", $"Unsupported bridge method: {request.Method}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -222,6 +230,48 @@ public sealed class BridgeStdioServer
|
|||||||
return HandleFavoritesList(@params);
|
return HandleFavoritesList(@params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object HandleFavoritesRemoveMany(JsonElement @params)
|
||||||
|
{
|
||||||
|
var startDir = GetString(@params, "projectRoot") ?? _startupProjectRoot ?? Directory.GetCurrentDirectory();
|
||||||
|
var loaded = WorkspaceLoader.FindAndLoad(startDir) ?? throw new BridgeValidationException("No workspace configuration found.");
|
||||||
|
var (workspace, workspaceRoot) = loaded;
|
||||||
|
|
||||||
|
if (@params.ValueKind != JsonValueKind.Object ||
|
||||||
|
!@params.TryGetProperty("items", out var itemsProp) ||
|
||||||
|
itemsProp.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
throw new BridgeValidationException("Missing required parameter 'items'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = new List<(string ProjectPath, string WorkflowId)>();
|
||||||
|
foreach (var item in itemsProp.EnumerateArray())
|
||||||
|
{
|
||||||
|
var path = item.TryGetProperty("projectPath", out var pp) && pp.ValueKind == JsonValueKind.String
|
||||||
|
? pp.GetString()
|
||||||
|
: null;
|
||||||
|
var workflowId = item.TryGetProperty("workflowId", out var wf) && wf.ValueKind == JsonValueKind.String
|
||||||
|
? wf.GetString()
|
||||||
|
: null;
|
||||||
|
if (string.IsNullOrWhiteSpace(path) || string.IsNullOrWhiteSpace(workflowId))
|
||||||
|
continue;
|
||||||
|
items.Add((Path.GetFullPath(path), workflowId!));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
return HandleFavoritesList(@params);
|
||||||
|
|
||||||
|
workspace.Favorites.RemoveAll(f =>
|
||||||
|
{
|
||||||
|
var resolved = WorkspaceLoader.ResolveFavoriteProjectRoot(workspaceRoot, f);
|
||||||
|
return items.Any(i =>
|
||||||
|
string.Equals(i.ProjectPath, resolved, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
string.Equals(i.WorkflowId, f.WorkflowId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
});
|
||||||
|
|
||||||
|
WorkspaceLoader.Save(workspaceRoot, workspace);
|
||||||
|
return HandleFavoritesList(@params);
|
||||||
|
}
|
||||||
|
|
||||||
private object HandleHistoryList(JsonElement @params)
|
private object HandleHistoryList(JsonElement @params)
|
||||||
{
|
{
|
||||||
var projectRoot = ResolveProjectRootForProjectScopedMethod(@params);
|
var projectRoot = ResolveProjectRootForProjectScopedMethod(@params);
|
||||||
@ -270,6 +320,150 @@ public sealed class BridgeStdioServer
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object HandleEnvProfilesSetActive(JsonElement @params)
|
||||||
|
{
|
||||||
|
var loaded = LoadProject(@params);
|
||||||
|
var profileId = GetRequiredString(@params, "profileId");
|
||||||
|
var envProfiles = loaded.Config.EnvProfiles ?? new EnvProfilesConfig
|
||||||
|
{
|
||||||
|
Active = profileId,
|
||||||
|
Profiles = []
|
||||||
|
};
|
||||||
|
|
||||||
|
var profileExists = envProfiles.Profiles.Any(p =>
|
||||||
|
string.Equals(p.Id, profileId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (!profileExists)
|
||||||
|
throw new BridgeValidationException($"Profile '{profileId}' does not exist.");
|
||||||
|
|
||||||
|
var updated = new DevToolConfig
|
||||||
|
{
|
||||||
|
Name = loaded.Config.Name,
|
||||||
|
Version = loaded.Config.Version,
|
||||||
|
Targets = loaded.Config.Targets,
|
||||||
|
Workflows = loaded.Config.Workflows,
|
||||||
|
Env = loaded.Config.Env,
|
||||||
|
EnvProfiles = new EnvProfilesConfig
|
||||||
|
{
|
||||||
|
Active = profileId,
|
||||||
|
Profiles = envProfiles.Profiles
|
||||||
|
},
|
||||||
|
Toolchains = loaded.Config.Toolchains,
|
||||||
|
Tooling = loaded.Config.Tooling,
|
||||||
|
Project = loaded.Config.Project,
|
||||||
|
Debug = loaded.Config.Debug
|
||||||
|
};
|
||||||
|
|
||||||
|
var save = SaveConfigWithBackup(loaded.ProjectRoot, updated);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
profileId,
|
||||||
|
save,
|
||||||
|
envProfiles = new
|
||||||
|
{
|
||||||
|
active = profileId,
|
||||||
|
profiles = envProfiles.Profiles
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private object HandleEnvProfilesSaveProfile(JsonElement @params)
|
||||||
|
{
|
||||||
|
var loaded = LoadProject(@params);
|
||||||
|
if (@params.ValueKind != JsonValueKind.Object ||
|
||||||
|
!@params.TryGetProperty("profile", out var profileProp) ||
|
||||||
|
profileProp.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
throw new BridgeValidationException("Missing required parameter 'profile'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = profileProp.TryGetProperty("id", out var idProp) && idProp.ValueKind == JsonValueKind.String
|
||||||
|
? idProp.GetString()
|
||||||
|
: null;
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
throw new BridgeValidationException("Profile id is required.");
|
||||||
|
|
||||||
|
var description = profileProp.TryGetProperty("description", out var dProp) && dProp.ValueKind == JsonValueKind.String
|
||||||
|
? dProp.GetString() ?? ""
|
||||||
|
: "";
|
||||||
|
|
||||||
|
var inherits = new List<string>();
|
||||||
|
if (profileProp.TryGetProperty("inherits", out var inhProp) && inhProp.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
foreach (var entry in inhProp.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (entry.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
var value = entry.GetString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
inherits.Add(value!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (profileProp.TryGetProperty("values", out var valuesProp) && valuesProp.ValueKind == JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
foreach (var kvp in valuesProp.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (kvp.Value.ValueKind == JsonValueKind.String)
|
||||||
|
values[kvp.Name] = kvp.Value.GetString() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var envProfiles = loaded.Config.EnvProfiles ?? new EnvProfilesConfig
|
||||||
|
{
|
||||||
|
Active = id!,
|
||||||
|
Profiles = []
|
||||||
|
};
|
||||||
|
var profiles = envProfiles.Profiles.ToList();
|
||||||
|
var existingIndex = profiles.FindIndex(p => string.Equals(p.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||||
|
var newProfile = new EnvProfileDefinition
|
||||||
|
{
|
||||||
|
Id = id!,
|
||||||
|
Description = description,
|
||||||
|
Inherits = inherits,
|
||||||
|
Values = values,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existingIndex >= 0)
|
||||||
|
profiles[existingIndex] = newProfile;
|
||||||
|
else
|
||||||
|
profiles.Add(newProfile);
|
||||||
|
|
||||||
|
var active = string.IsNullOrWhiteSpace(envProfiles.Active) ? id! : envProfiles.Active;
|
||||||
|
var updated = new DevToolConfig
|
||||||
|
{
|
||||||
|
Name = loaded.Config.Name,
|
||||||
|
Version = loaded.Config.Version,
|
||||||
|
Targets = loaded.Config.Targets,
|
||||||
|
Workflows = loaded.Config.Workflows,
|
||||||
|
Env = loaded.Config.Env,
|
||||||
|
EnvProfiles = new EnvProfilesConfig
|
||||||
|
{
|
||||||
|
Active = active,
|
||||||
|
Profiles = profiles
|
||||||
|
},
|
||||||
|
Toolchains = loaded.Config.Toolchains,
|
||||||
|
Tooling = loaded.Config.Tooling,
|
||||||
|
Project = loaded.Config.Project,
|
||||||
|
Debug = loaded.Config.Debug
|
||||||
|
};
|
||||||
|
|
||||||
|
var save = SaveConfigWithBackup(loaded.ProjectRoot, updated);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
profile = newProfile,
|
||||||
|
save,
|
||||||
|
envProfiles = new
|
||||||
|
{
|
||||||
|
active,
|
||||||
|
profiles
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<object> HandleDoctorRunAsync(JsonElement @params, CancellationToken cancellationToken)
|
private async Task<object> HandleDoctorRunAsync(JsonElement @params, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var loaded = LoadProject(@params);
|
var loaded = LoadProject(@params);
|
||||||
@ -303,6 +497,138 @@ public sealed class BridgeStdioServer
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object HandleEnvVarsList(JsonElement @params)
|
||||||
|
{
|
||||||
|
var loaded = LoadProject(@params);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
env = loaded.Config.Env
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private object HandleEnvVarsSave(JsonElement @params)
|
||||||
|
{
|
||||||
|
var loaded = LoadProject(@params);
|
||||||
|
if (@params.ValueKind != JsonValueKind.Object ||
|
||||||
|
!@params.TryGetProperty("env", out var envProp) ||
|
||||||
|
envProp.ValueKind != JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
throw new BridgeValidationException("Missing required parameter 'env'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var env = new List<EnvVarDef>();
|
||||||
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var item in envProp.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (item.ValueKind != JsonValueKind.Object)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var key = item.TryGetProperty("key", out var keyProp) && keyProp.ValueKind == JsonValueKind.String
|
||||||
|
? keyProp.GetString()
|
||||||
|
: null;
|
||||||
|
if (string.IsNullOrWhiteSpace(key))
|
||||||
|
throw new BridgeValidationException("Each env var requires a non-empty 'key'.");
|
||||||
|
if (!seen.Add(key!))
|
||||||
|
throw new BridgeValidationException($"Duplicate env key detected: '{key}'.");
|
||||||
|
|
||||||
|
var description = item.TryGetProperty("description", out var descProp) && descProp.ValueKind == JsonValueKind.String
|
||||||
|
? descProp.GetString() ?? ""
|
||||||
|
: "";
|
||||||
|
var defaultValue = item.TryGetProperty("default", out var defProp) && defProp.ValueKind == JsonValueKind.String
|
||||||
|
? defProp.GetString() ?? ""
|
||||||
|
: "";
|
||||||
|
var options = new List<string>();
|
||||||
|
if (item.TryGetProperty("options", out var optionsProp) && optionsProp.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
foreach (var opt in optionsProp.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (opt.ValueKind == JsonValueKind.String)
|
||||||
|
options.Add(opt.GetString() ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env.Add(new EnvVarDef
|
||||||
|
{
|
||||||
|
Key = key!,
|
||||||
|
Description = description,
|
||||||
|
DefaultValue = defaultValue,
|
||||||
|
Options = options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated = new DevToolConfig
|
||||||
|
{
|
||||||
|
Name = loaded.Config.Name,
|
||||||
|
Version = loaded.Config.Version,
|
||||||
|
Targets = loaded.Config.Targets,
|
||||||
|
Workflows = loaded.Config.Workflows,
|
||||||
|
Env = env,
|
||||||
|
EnvProfiles = loaded.Config.EnvProfiles,
|
||||||
|
Toolchains = loaded.Config.Toolchains,
|
||||||
|
Tooling = loaded.Config.Tooling,
|
||||||
|
Project = loaded.Config.Project,
|
||||||
|
Debug = loaded.Config.Debug
|
||||||
|
};
|
||||||
|
|
||||||
|
var save = SaveConfigWithBackup(loaded.ProjectRoot, updated);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
env,
|
||||||
|
save
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private object HandleSetupAutofixDirs(JsonElement @params)
|
||||||
|
{
|
||||||
|
var loaded = LoadProject(@params);
|
||||||
|
var missingDirs = _doctorFixes.FindMissingWorkingDirectories(loaded.Config, loaded.ProjectRoot);
|
||||||
|
var result = _doctorFixes.CreateMissingWorkingDirectories(missingDirs);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
missingDirectories = missingDirs,
|
||||||
|
result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private object HandleSetupMigrateLegacy(JsonElement @params)
|
||||||
|
{
|
||||||
|
var loaded = LoadProject(@params);
|
||||||
|
var result = _doctorFixes.ApplyLegacyMigration(loaded.ProjectRoot);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private object HandleSetupApplyRecommendedConfig(JsonElement @params)
|
||||||
|
{
|
||||||
|
var loaded = LoadProject(@params);
|
||||||
|
var update = _setupConfigService.ApplyRecommendedDefaults(loaded.Config);
|
||||||
|
if (update.Changes.Count == 0)
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
changes = update.Changes,
|
||||||
|
applied = false,
|
||||||
|
message = "No recommended config changes were needed."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var save = SaveConfigWithBackup(loaded.ProjectRoot, update.Config);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
projectRoot = loaded.ProjectRoot,
|
||||||
|
changes = update.Changes,
|
||||||
|
applied = save.Success,
|
||||||
|
save
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private LoadedProjectConfig LoadProject(JsonElement @params)
|
private LoadedProjectConfig LoadProject(JsonElement @params)
|
||||||
{
|
{
|
||||||
var startDir = GetString(@params, "projectRoot") ?? _startupProjectRoot ?? Directory.GetCurrentDirectory();
|
var startDir = GetString(@params, "projectRoot") ?? _startupProjectRoot ?? Directory.GetCurrentDirectory();
|
||||||
@ -351,6 +677,25 @@ public sealed class BridgeStdioServer
|
|||||||
|
|
||||||
private static BridgeResponseEnvelope Error(string? id, string code, string message, object? details = null) =>
|
private static BridgeResponseEnvelope Error(string? id, string code, string message, object? details = null) =>
|
||||||
new(id, false, null, new BridgeErrorEnvelope(code, message, details));
|
new(id, false, null, new BridgeErrorEnvelope(code, message, details));
|
||||||
|
|
||||||
|
private static LegacyMigrationApplyResult SaveConfigWithBackup(string projectRoot, DevToolConfig config)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var path = ConfigLoader.FindConfigPath(projectRoot);
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
return new LegacyMigrationApplyResult(false, "Could not find devtool.json for saving.");
|
||||||
|
|
||||||
|
var backup = path + $".bak-{DateTimeOffset.Now:yyyyMMdd-HHmmss}";
|
||||||
|
File.Copy(path, backup, overwrite: false);
|
||||||
|
File.WriteAllText(path, ConfigBootstrapper.ToJson(config));
|
||||||
|
return new LegacyMigrationApplyResult(true, "Saved updated devtool.json.", BackupPath: backup, ConfigPath: path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new LegacyMigrationApplyResult(false, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BridgeValidationException(string message) : Exception(message);
|
public sealed class BridgeValidationException(string message) : Exception(message);
|
||||||
|
|||||||
@ -34,5 +34,10 @@ npm run tauri dev
|
|||||||
- workflow + debug run
|
- workflow + debug run
|
||||||
- failure card rendering from summary payload
|
- failure card rendering from summary payload
|
||||||
- run context + lifecycle panel
|
- run context + lifecycle panel
|
||||||
- workspace load/add/add+init
|
- workspace load/add/add+init + project context switcher
|
||||||
|
- favorite quick-action runner ("Run Favorite")
|
||||||
- run history + events viewer
|
- run history + events viewer
|
||||||
|
- workspace/favorites grouping + bulk actions
|
||||||
|
- setup apply actions (autofix dirs, legacy migration, recommended config)
|
||||||
|
- keyboard UX (`Ctrl+K` command palette, `?` shortcut help)
|
||||||
|
- env management editor (`envProfiles` + grid-based `env[]` definitions editor)
|
||||||
|
|||||||
@ -45,6 +45,45 @@
|
|||||||
<input id="favorite-label" placeholder="Quick Build" autocomplete="off" />
|
<input id="favorite-label" placeholder="Quick Build" autocomplete="off" />
|
||||||
<button id="toggle-favorite-btn" type="button">Toggle Favorite</button>
|
<button id="toggle-favorite-btn" type="button">Toggle Favorite</button>
|
||||||
</div>
|
</div>
|
||||||
|
<h3>Workspace Controls</h3>
|
||||||
|
<div class="row">
|
||||||
|
<input id="project-filter" placeholder="Search configured projects..." autocomplete="off" />
|
||||||
|
<select id="project-sort">
|
||||||
|
<option value="name">Sort: name</option>
|
||||||
|
<option value="path">Sort: path</option>
|
||||||
|
</select>
|
||||||
|
<select id="project-group">
|
||||||
|
<option value="tool">Group: tool family</option>
|
||||||
|
<option value="path">Group: top folder</option>
|
||||||
|
<option value="none">Group: none</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input id="switch-run-workflow" placeholder="workflow id for Switch + Run (e.g. build)" autocomplete="off" />
|
||||||
|
<button id="bulk-run-projects-btn" type="button">Run Filtered Projects</button>
|
||||||
|
</div>
|
||||||
|
<h3>Configured Projects (switch context)</h3>
|
||||||
|
<div id="configured-projects" class="list"></div>
|
||||||
|
<h3>Favorites Controls</h3>
|
||||||
|
<div class="row">
|
||||||
|
<input id="favorite-filter" placeholder="Search favorites..." autocomplete="off" />
|
||||||
|
<select id="favorite-sort">
|
||||||
|
<option value="label">Sort: label</option>
|
||||||
|
<option value="workflow">Sort: workflow</option>
|
||||||
|
<option value="project">Sort: project</option>
|
||||||
|
</select>
|
||||||
|
<select id="favorite-group">
|
||||||
|
<option value="project">Group: project</option>
|
||||||
|
<option value="workflow">Group: workflow</option>
|
||||||
|
<option value="none">Group: none</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<button id="bulk-run-favorites-btn" type="button">Run Filtered Favorites</button>
|
||||||
|
<button id="bulk-remove-favorites-btn" type="button">Remove Filtered Favorites</button>
|
||||||
|
</div>
|
||||||
|
<h3>Favorites (run now)</h3>
|
||||||
|
<div id="favorite-actions" class="list"></div>
|
||||||
<pre id="workspace-output"></pre>
|
<pre id="workspace-output"></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -98,15 +137,80 @@
|
|||||||
<button id="load-doctor-btn" type="button">Run Doctor</button>
|
<button id="load-doctor-btn" type="button">Run Doctor</button>
|
||||||
<button id="load-setup-plan-btn" type="button">Setup Plan (read-only)</button>
|
<button id="load-setup-plan-btn" type="button">Setup Plan (read-only)</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<button id="apply-autofix-dirs-btn" type="button">Apply Autofix: Missing Dirs</button>
|
||||||
|
<button id="apply-migrate-legacy-btn" type="button">Apply Legacy Migration</button>
|
||||||
|
<button id="apply-recommended-config-btn" type="button">Apply Recommended Config</button>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input id="env-resolve-id" placeholder="env profile id (optional)" autocomplete="off" />
|
<input id="env-resolve-id" placeholder="env profile id (optional)" autocomplete="off" />
|
||||||
<button id="load-env-btn" type="button">Load Env Profiles + Resolve</button>
|
<button id="load-env-btn" type="button">Load Env Profiles + Resolve</button>
|
||||||
</div>
|
</div>
|
||||||
<h3>Environment</h3>
|
<h3>Environment</h3>
|
||||||
<pre id="env-output"></pre>
|
<pre id="env-output"></pre>
|
||||||
|
<h3>Env Profile Editor</h3>
|
||||||
|
<div class="stack">
|
||||||
|
<div class="row">
|
||||||
|
<select id="env-profile-select"></select>
|
||||||
|
<button id="env-load-profile-btn" type="button">Load Profile</button>
|
||||||
|
<button id="env-set-active-btn" type="button">Set Active</button>
|
||||||
|
</div>
|
||||||
|
<label for="env-profile-id">Profile Id</label>
|
||||||
|
<input id="env-profile-id" placeholder="dev" autocomplete="off" />
|
||||||
|
<label for="env-profile-desc">Description</label>
|
||||||
|
<input id="env-profile-desc" placeholder="Local development defaults" autocomplete="off" />
|
||||||
|
<label for="env-profile-inherits">Inherits (comma separated)</label>
|
||||||
|
<input id="env-profile-inherits" placeholder="dev,ci" autocomplete="off" />
|
||||||
|
<label for="env-profile-values">Values (KEY=VALUE per line)</label>
|
||||||
|
<textarea id="env-profile-values" rows="8"></textarea>
|
||||||
|
<div class="row">
|
||||||
|
<button id="env-save-profile-btn" type="button">Save Profile</button>
|
||||||
|
<button id="env-new-profile-btn" type="button">New Profile Draft</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>Env Var Definitions Editor</h3>
|
||||||
|
<div class="stack">
|
||||||
|
<p>Format: <code>KEY|description|default|option1,option2</code> (one per line)</p>
|
||||||
|
<div class="row">
|
||||||
|
<button id="env-vars-add-row-btn" type="button">Add Row</button>
|
||||||
|
<button id="env-vars-sync-to-advanced-btn" type="button">Sync Grid → Advanced</button>
|
||||||
|
<button id="env-vars-sync-from-advanced-btn" type="button">Sync Advanced → Grid</button>
|
||||||
|
<button id="env-vars-toggle-advanced-btn" type="button">Toggle Advanced Mode</button>
|
||||||
|
</div>
|
||||||
|
<div id="env-vars-grid" class="env-grid"></div>
|
||||||
|
<textarea id="env-vars-editor" rows="10"></textarea>
|
||||||
|
<div class="row">
|
||||||
|
<button id="env-vars-load-btn" type="button">Load env[]</button>
|
||||||
|
<button id="env-vars-validate-btn" type="button">Validate</button>
|
||||||
|
<button id="env-vars-save-btn" type="button">Save env[]</button>
|
||||||
|
</div>
|
||||||
|
<pre id="env-vars-validation"></pre>
|
||||||
|
</div>
|
||||||
<h3>Diagnostics</h3>
|
<h3>Diagnostics</h3>
|
||||||
<pre id="diagnostics-output"></pre>
|
<pre id="diagnostics-output"></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<h2>Keyboard</h2>
|
||||||
|
<p><code>Ctrl+K</code> command palette, <code>?</code> shortcut help.</p>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<div id="palette" class="overlay hidden">
|
||||||
|
<div class="dialog">
|
||||||
|
<h3>Command Palette</h3>
|
||||||
|
<input id="palette-filter" placeholder="Filter commands..." autocomplete="off" />
|
||||||
|
<div id="palette-list" class="list"></div>
|
||||||
|
<button id="palette-close-btn" type="button">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="shortcuts" class="overlay hidden">
|
||||||
|
<div class="dialog">
|
||||||
|
<h3>Shortcut Help</h3>
|
||||||
|
<pre id="shortcut-help"></pre>
|
||||||
|
<button id="shortcuts-close-btn" type="button">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -101,3 +101,10 @@ export type SetupPlanResult = {
|
|||||||
}>;
|
}>;
|
||||||
recommendedChanges: string[];
|
recommendedChanges: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EnvVarDefinition = {
|
||||||
|
key: string;
|
||||||
|
description: string;
|
||||||
|
default: string;
|
||||||
|
options: string[];
|
||||||
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import type {
|
import type {
|
||||||
DoctorReport,
|
DoctorReport,
|
||||||
|
EnvVarDefinition,
|
||||||
EnvProfilesResult,
|
EnvProfilesResult,
|
||||||
EnvResolveResult,
|
EnvResolveResult,
|
||||||
RunEventLogFile,
|
RunEventLogFile,
|
||||||
@ -78,6 +79,17 @@ export function toggleFavorite(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeFavoritesMany(
|
||||||
|
projectRoot: string | null,
|
||||||
|
items: Array<{ projectPath: string; workflowId: string }>,
|
||||||
|
): Promise<WorkspaceFavorite[]> {
|
||||||
|
return bridgeCall<WorkspaceFavorite[]>(
|
||||||
|
"favorites.removeMany",
|
||||||
|
{ items },
|
||||||
|
projectRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function listHistory(
|
export function listHistory(
|
||||||
projectRoot: string | null,
|
projectRoot: string | null,
|
||||||
limit: number,
|
limit: number,
|
||||||
@ -115,6 +127,54 @@ export function resolveEnvProfile(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setActiveEnvProfile(
|
||||||
|
projectRoot: string | null,
|
||||||
|
profileId: string,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
return bridgeCall<Record<string, unknown>>(
|
||||||
|
"envProfiles.setActive",
|
||||||
|
{ profileId },
|
||||||
|
projectRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveEnvProfile(
|
||||||
|
projectRoot: string | null,
|
||||||
|
profile: {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
inherits: string[];
|
||||||
|
values: Record<string, string>;
|
||||||
|
},
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
return bridgeCall<Record<string, unknown>>(
|
||||||
|
"envProfiles.saveProfile",
|
||||||
|
{ profile },
|
||||||
|
projectRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listEnvVars(
|
||||||
|
projectRoot: string | null,
|
||||||
|
): Promise<{ projectRoot: string; env: EnvVarDefinition[] }> {
|
||||||
|
return bridgeCall<{ projectRoot: string; env: EnvVarDefinition[] }>(
|
||||||
|
"envVars.list",
|
||||||
|
{},
|
||||||
|
projectRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveEnvVars(
|
||||||
|
projectRoot: string | null,
|
||||||
|
env: EnvVarDefinition[],
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
return bridgeCall<Record<string, unknown>>(
|
||||||
|
"envVars.save",
|
||||||
|
{ env },
|
||||||
|
projectRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function runDoctor(projectRoot: string | null): Promise<DoctorReport> {
|
export function runDoctor(projectRoot: string | null): Promise<DoctorReport> {
|
||||||
return bridgeCall<DoctorReport>("doctor.run", {}, projectRoot);
|
return bridgeCall<DoctorReport>("doctor.run", {}, projectRoot);
|
||||||
}
|
}
|
||||||
@ -122,3 +182,25 @@ export function runDoctor(projectRoot: string | null): Promise<DoctorReport> {
|
|||||||
export function setupPlan(projectRoot: string | null): Promise<SetupPlanResult> {
|
export function setupPlan(projectRoot: string | null): Promise<SetupPlanResult> {
|
||||||
return bridgeCall<SetupPlanResult>("setup.plan", {}, projectRoot);
|
return bridgeCall<SetupPlanResult>("setup.plan", {}, projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setupAutofixDirs(
|
||||||
|
projectRoot: string | null,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
return bridgeCall<Record<string, unknown>>("setup.autofixDirs", {}, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupMigrateLegacy(
|
||||||
|
projectRoot: string | null,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
return bridgeCall<Record<string, unknown>>("setup.migrateLegacy", {}, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupApplyRecommendedConfig(
|
||||||
|
projectRoot: string | null,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
return bridgeCall<Record<string, unknown>>(
|
||||||
|
"setup.applyRecommendedConfig",
|
||||||
|
{},
|
||||||
|
projectRoot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -45,12 +45,49 @@ body {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 8px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #1f2937;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background: #0b1629;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
border: 1px dashed #334155;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #93c5fd;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item code {
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
|
select,
|
||||||
|
textarea,
|
||||||
button {
|
button {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #3f4f69;
|
border: 1px solid #3f4f69;
|
||||||
@ -65,6 +102,63 @@ input {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #3f4f69;
|
||||||
|
padding: 0.6em 0.8em;
|
||||||
|
font-size: 0.95em;
|
||||||
|
font-family: Consolas, "Courier New", monospace;
|
||||||
|
color: #e8ecf1;
|
||||||
|
background-color: #122033;
|
||||||
|
}
|
||||||
|
|
||||||
|
.env-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.env-grid-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.2fr 1.4fr 1fr 1.4fr auto;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.env-grid-header {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #dbeafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip button {
|
||||||
|
margin-left: 6px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #fca5a5;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -120,3 +214,27 @@ code {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(2, 6, 23, 0.75);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
width: min(760px, 92vw);
|
||||||
|
max-height: 82vh;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #0b1220;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user