feat(workspace): allow creation without an initial repository
Keep bootstrap, signing identity, config and resource-key allocation atomic with optional Repository intent. Preserve repository-present fingerprints for saved receipts; replay reads current authority at the initial key and never recreates missing assets. Provide explicit repository-free CLI creation, optional TUI/Web creation inputs, nullable generated contracts, and valid empty catalog/Worker paths. Retain existing authenticated non-owner viewing policy pending the T-717 authorization decision. Validation: root cargo check; server tests 639 lib + 11 bin; server-api 70 default / 77 typescript; client 52 lib + 2 integration; tui 337; yoi 11 lib + 120 bin. OpenAPI/TS freshness, cargo fmt and diff checks pass. Web check/build, 18 model tests, 268 component tests and Chromium 8 width/theme combinations pass; before/after visual evidence is documented in web/workspace/test/visual-review. Full Web suite has one independently reproduced baseline Ticket source-policy failure.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Optional initial repository validation (T-717 Web portion)
|
||||
|
||||
These scenarios use the production static Workspace build and the existing
|
||||
`tools/web-ux` capture/process harness. The loopback proxy only extends the shared
|
||||
Home fixture's API responses for empty Worker lists and launch options; it does
|
||||
not replace the application shell, styles, or components. No real Workspace,
|
||||
account, repository, Runtime, or Worker is created.
|
||||
|
||||
## Run
|
||||
|
||||
From `web/workspace`, with the repository's pinned Chromium available:
|
||||
|
||||
```sh
|
||||
deno task build
|
||||
deno task test:browser:empty-workspace
|
||||
```
|
||||
|
||||
The browser test covers light/dark at 1440, 768, 390, and 320px: optional Git
|
||||
native validation, opting out after entering repository values, null creation
|
||||
response/navigation, zero-repository catalog reopening, normal embedded Worker
|
||||
submission without Workdir or Ticket assignment, and remote Runtime Workdir
|
||||
requirements. It uses dynamically allocated loopback ports and the existing
|
||||
owned-process cleanup harness. Screenshots and logs are ignored artifacts under
|
||||
`.svelte-kit/t717/browser`.
|
||||
|
||||
From `tools/web-ux`:
|
||||
|
||||
```sh
|
||||
deno task web-ux capture \
|
||||
--scenario ../../web/workspace/test/visual-review/optional-repository.json \
|
||||
--output ../../web/workspace/.svelte-kit/t717/visual --run-id empty-workspace
|
||||
|
||||
deno task web-ux capture \
|
||||
--scenario ../../web/workspace/test/visual-review/optional-repository-interactions.json \
|
||||
--output ../../web/workspace/.svelte-kit/t717/visual --run-id creation-interactions
|
||||
```
|
||||
|
||||
The interaction scenario intentionally rejects creation with HTTP400. Capture
|
||||
exit status 2 and the recorded request/console/visible validation errors are
|
||||
expected for `validation-error`, not evidence of a successful creation. No other
|
||||
capture error is expected. To repeat dark captures, use a scenario copy with
|
||||
`colorScheme: "dark"`, preserving the process cwd paths.
|
||||
|
||||
## Implementer-owned visual review
|
||||
|
||||
- Baseline revision: `e939cd4e4b1f6b63b81411fd7766062f2d69ad0d`.
|
||||
- Before: `.svelte-kit/t717/visual/before-final-light` and `before-dark`.
|
||||
These use an isolated build under `.svelte-kit/t717/baseline-source`, with the
|
||||
changed production form, catalog API, model, and CSS restored from baseline
|
||||
Git source. The original pre-edit light capture is also retained as
|
||||
`.svelte-kit/t717/visual/before-valid`.
|
||||
- After: `.svelte-kit/t717/visual/final-light`, `final-dark`,
|
||||
`final-interactions-light`, and `final-interactions-dark`.
|
||||
- Comparisons: `.svelte-kit/t717/visual/final-comparison-light` and
|
||||
`final-comparison-dark`.
|
||||
- Personas: synthetic owner and non-owner permission projections; catalog
|
||||
creation/reopening uses the same synthetic account.
|
||||
- Viewports: 1440, 768, 390px; additional browser screenshots/checks at 320px.
|
||||
- Themes: light and dark.
|
||||
- States: zero repositories, empty Home/Sidebar, representative member work
|
||||
records, long catalog names, unchecked/checked optional repository form,
|
||||
populated inputs, and intentional Backend validation failure.
|
||||
- Reviewed by the implementer using `ViewImage`: before/after contact sheets,
|
||||
individual narrow/mobile populated-form screenshots, and the final mobile
|
||||
dark validation-error screenshot. Reviewed context bundles and accessibility
|
||||
snapshots for document status, control names/state, screenshot hashes,
|
||||
console/page/request errors, and capture points.
|
||||
- Fixed: catalog/form clipping at narrow content widths; repository field
|
||||
disclosure/required-state handling. Chromium tests also found and fixed the
|
||||
existing repository-key HTML pattern's invalid modern-regexp character class.
|
||||
- Result: pass. All baseline and final main captures have document status 200
|
||||
and no retained browser/UI errors. Only the intentional HTTP400 interaction
|
||||
errors remain. Home, member Sidebar, and Worker launch comparisons show zero
|
||||
changed pixels; catalog changes are confined to the intended surface.
|
||||
|
||||
## Validation and limitations
|
||||
|
||||
- `deno task check`: 0 errors, 0 warnings.
|
||||
- Targeted model/catalog Deno tests: 18 passed.
|
||||
- `deno task test:component`: 29 files, 268 tests passed.
|
||||
- `deno task test:browser:empty-workspace`: passed (8 theme/width combinations).
|
||||
- `deno task build`: passed.
|
||||
- Web-scoped `git diff --check HEAD -- .`: passed.
|
||||
- `deno task test`: 522 passed, 1 pre-existing failure in
|
||||
`src/lib/workspace/console/worker-console.ui.test.ts`,
|
||||
“workspace Tickets surface provides Kanban and lifecycle controls”. It fails
|
||||
the unchanged Ticket detail source assertion at line 429. The same failure
|
||||
was reproduced against the isolated baseline source. Evidence:
|
||||
`.svelte-kit/t717/unit.log` and `baseline-ticket-failure.log`.
|
||||
- Canonical OpenAPI and generated TypeScript were regenerated together.
|
||||
The Web parser uses the generated nullable creation-response type directly.
|
||||
Backend persistence/idempotency is covered separately by Store/catalog and
|
||||
real HTTP boundary tests in `yoi-workspace-server`.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Extends the existing Web UX fixture at the API boundary, never the production shell/CSS.
|
||||
// Both loopback servers are owned and cleaned up by the Web UX capture harness.
|
||||
const port = Number(Deno.args[0]);
|
||||
const upstream = Deno.args[1];
|
||||
if (!Number.isInteger(port) || !upstream) {
|
||||
throw new Error("usage: server <port> <upstream-url>");
|
||||
}
|
||||
|
||||
import { emptyLaunchOptions } from "../../src/lib/workspace/sidebar/worker-launch.test-fixtures.ts";
|
||||
import { dashboardFixture } from "../../src/lib/workspace/home/dashboard.test-fixtures.ts";
|
||||
|
||||
Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname === "/api/workspaces" && request.method === "POST") {
|
||||
return Response.json({
|
||||
message:
|
||||
"Workspace display name was rejected by Backend validation. Choose another display name and retry; no Workspace was created.",
|
||||
}, { status: 400 });
|
||||
}
|
||||
const match = url.pathname.match(/^\/api\/w\/([^/]+)(\/.*)$/);
|
||||
if (match) {
|
||||
const [, id, path] = match;
|
||||
// The shared catalog includes a deliberate failure persona, which can be hover-preloaded.
|
||||
// This scenario tests zero repositories, not that separate failure contract.
|
||||
if (id === "home-error" && path === "/merge-requests") {
|
||||
return Response.json(
|
||||
dashboardFixture(path, url.searchParams, "home-empty"),
|
||||
);
|
||||
}
|
||||
if (path === "/workers") {
|
||||
return Response.json({
|
||||
workspace_id: id,
|
||||
limit: 100,
|
||||
items: [],
|
||||
source: "fixture",
|
||||
diagnostics: [],
|
||||
});
|
||||
}
|
||||
if (path === "/workers/launch-options") {
|
||||
return Response.json(emptyLaunchOptions(id));
|
||||
}
|
||||
}
|
||||
// Forward HTTP and bridge the existing workspace subscription fixture.
|
||||
const target = new URL(url.pathname + url.search, upstream);
|
||||
if (request.headers.get("upgrade") === "websocket") {
|
||||
const { socket, response } = Deno.upgradeWebSocket(request);
|
||||
target.protocol = "ws:";
|
||||
const peer = new WebSocket(target);
|
||||
const pending: string[] = [];
|
||||
socket.onmessage = (event) =>
|
||||
peer.readyState === WebSocket.OPEN
|
||||
? peer.send(event.data)
|
||||
: pending.push(String(event.data));
|
||||
peer.onopen = () => {
|
||||
for (const frame of pending) peer.send(frame);
|
||||
};
|
||||
peer.onmessage = (event) => {
|
||||
if (socket.readyState === WebSocket.OPEN) socket.send(event.data);
|
||||
};
|
||||
socket.onclose = () => peer.close();
|
||||
peer.onclose = () => socket.close();
|
||||
return response;
|
||||
}
|
||||
return await fetch(new Request(target, request));
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
import { assert, assertEquals } from "jsr:@std/assert@1.0.19";
|
||||
import { chromium } from "npm:playwright@1.59.1";
|
||||
import { loadScenario } from "../../../../tools/web-ux/src/scenario.ts";
|
||||
import {
|
||||
startOwnedProcesses,
|
||||
stopOwnedProcesses,
|
||||
} from "../../../../tools/web-ux/src/processes.ts";
|
||||
import { creationResponse } from "../../src/lib/workspace/api/workspace-catalog.test-fixtures.ts";
|
||||
|
||||
// Browser-only contract over the production static build and loopback API fixtures;
|
||||
// no actual Backend, Runtime, Worker, account or repository is created.
|
||||
Deno.test("production empty Workspace supports optional Git validation, reopening, and Workdir-less Worker submission at all review widths", async () => {
|
||||
const scenarioPath =
|
||||
new URL("./optional-repository.json", import.meta.url).pathname;
|
||||
const scenario = await loadScenario(scenarioPath);
|
||||
async function freePort() {
|
||||
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
|
||||
const port = (listener.addr as Deno.NetAddr).port;
|
||||
listener.close();
|
||||
return port;
|
||||
}
|
||||
const upstreamPort = await freePort();
|
||||
let proxyPort = await freePort();
|
||||
while (proxyPort === upstreamPort) proxyPort = await freePort();
|
||||
scenario.baseUrl = `http://127.0.0.1:${proxyPort}`;
|
||||
scenario.processes![0].args![4] = String(upstreamPort);
|
||||
scenario.processes![0].readyUrl = `http://127.0.0.1:${upstreamPort}/health`;
|
||||
scenario.processes![1].args![3] = String(proxyPort);
|
||||
scenario.processes![1].args![4] = `http://127.0.0.1:${upstreamPort}`;
|
||||
scenario.processes![1].readyUrl = `${scenario.baseUrl}/health`;
|
||||
const output =
|
||||
new URL("../../.svelte-kit/t717/browser/", import.meta.url).pathname;
|
||||
const processes = await startOwnedProcesses(
|
||||
scenario.processes!,
|
||||
scenarioPath,
|
||||
`${output}/process-logs`,
|
||||
[],
|
||||
);
|
||||
try {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
try {
|
||||
for (const colorScheme of ["light", "dark"] as const) {
|
||||
for (const width of [1440, 768, 390, 320]) {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width, height: 900 },
|
||||
colorScheme,
|
||||
reducedMotion: "reduce",
|
||||
});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", (error) => errors.push(String(error)));
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") errors.push(message.text());
|
||||
});
|
||||
page.on("response", (response) => {
|
||||
if (response.status() >= 400) {
|
||||
errors.push(`${response.status()} ${response.url()}`);
|
||||
}
|
||||
});
|
||||
const creationBodies: Record<string, unknown>[] = [];
|
||||
await page.route("**/api/workspaces", async (route) => {
|
||||
if (route.request().method() !== "POST") {
|
||||
return await route.continue();
|
||||
}
|
||||
creationBodies.push(route.request().postDataJSON());
|
||||
await route.fulfill({ json: creationResponse() });
|
||||
});
|
||||
await page.goto(scenario.baseUrl!);
|
||||
const name = page.getByLabel("Workspace display name");
|
||||
await name.fill("New Workspace");
|
||||
const toggle = page.getByRole("checkbox", {
|
||||
name: "Add an initial repository (optional)",
|
||||
});
|
||||
await toggle.check();
|
||||
await page.getByRole("button", {
|
||||
name: "Create Workspace",
|
||||
exact: true,
|
||||
}).click();
|
||||
assertEquals(creationBodies.length, 0); // Native required fields stop partial repository submission.
|
||||
await page.getByLabel("Repository key", { exact: true }).fill(
|
||||
"Invalid key",
|
||||
);
|
||||
await page.getByLabel("Initial repository absolute path or URI")
|
||||
.fill("/srv/platform");
|
||||
assertEquals(
|
||||
await page.getByLabel("Repository key", { exact: true }).evaluate(
|
||||
(input) =>
|
||||
(input as unknown as { checkValidity(): boolean })
|
||||
.checkValidity(),
|
||||
),
|
||||
false,
|
||||
);
|
||||
await page.getByLabel("Repository key", { exact: true }).fill(
|
||||
"platform",
|
||||
);
|
||||
assertEquals(
|
||||
await page.locator("form").evaluate((form) =>
|
||||
(form as unknown as { checkValidity(): boolean })
|
||||
.checkValidity()
|
||||
),
|
||||
true,
|
||||
);
|
||||
await Deno.mkdir(output, { recursive: true });
|
||||
await page.screenshot({
|
||||
path: `${output}/${colorScheme}-${width}-repository.png`,
|
||||
fullPage: true,
|
||||
});
|
||||
assertEquals(
|
||||
await page.evaluate(
|
||||
"document.documentElement.scrollWidth > innerWidth",
|
||||
),
|
||||
false,
|
||||
`${colorScheme} ${width}px`,
|
||||
);
|
||||
await toggle.uncheck();
|
||||
await page.getByRole("button", {
|
||||
name: "Create Workspace",
|
||||
exact: true,
|
||||
}).click();
|
||||
await page.waitForURL("**/w/home-empty");
|
||||
assertEquals(creationBodies.length, 1);
|
||||
assertEquals(creationBodies[0].repository, null);
|
||||
await page.locator("[data-feed-ready=home-active]").waitFor();
|
||||
assertEquals(await page.locator("main [role=alert]").count(), 0);
|
||||
// Reopening is based on Workspace identity, not a repository record.
|
||||
await page.goto(scenario.baseUrl!);
|
||||
await page.locator('a.workspace-catalog-card[href="/w/home-empty"]')
|
||||
.click();
|
||||
await page.waitForURL("**/w/home-empty");
|
||||
await page.goto(`${scenario.baseUrl}/w/home-empty/workers/new`);
|
||||
await page.locator(".worker-launch-form").waitFor();
|
||||
assert(
|
||||
await page.getByRole("button", { name: "Start Worker" })
|
||||
.isEnabled(),
|
||||
);
|
||||
await page.getByRole("combobox", { name: "Runtime", exact: true })
|
||||
.selectOption("remote");
|
||||
assert(
|
||||
await page.getByRole("button", { name: "Start Worker" })
|
||||
.isDisabled(),
|
||||
);
|
||||
await page.getByLabel("Attachment 1 Workdir").selectOption(
|
||||
"__new_working_directory__",
|
||||
);
|
||||
assert(
|
||||
await page.getByRole("button", {
|
||||
name: "Create Workdir",
|
||||
exact: true,
|
||||
}).isDisabled(),
|
||||
);
|
||||
await page.getByRole("combobox", { name: "Runtime", exact: true })
|
||||
.selectOption("embedded");
|
||||
const submitted = page.waitForRequest((request) =>
|
||||
request.url().endsWith("/api/w/home-empty/workers") &&
|
||||
request.method() === "POST"
|
||||
);
|
||||
await page.route("**/api/w/home-empty/workers", async (route) => {
|
||||
if (route.request().method() !== "POST") {
|
||||
return await route.continue();
|
||||
}
|
||||
// Successful, synthetic launch projection; the test only exercises browser/API wiring.
|
||||
await route.fulfill({
|
||||
json: {
|
||||
workspace_id: "home-empty",
|
||||
runtime_id: "embedded",
|
||||
worker_id: "fixture-worker",
|
||||
console_href: "/w/home-empty",
|
||||
diagnostics: [],
|
||||
worker: {
|
||||
runtime_id: "embedded",
|
||||
worker_id: "fixture-worker",
|
||||
host_id: "fixture-host",
|
||||
display_name: "Worker",
|
||||
label: "Worker",
|
||||
profile: "builtin:companion",
|
||||
singleton_key: null,
|
||||
tags: [],
|
||||
workspace: {
|
||||
visibility: "workspace",
|
||||
identity: "home-empty",
|
||||
workspace_id: "home-empty",
|
||||
},
|
||||
state: "idle",
|
||||
last_seen_at: null,
|
||||
pinned: false,
|
||||
retention_state: "active",
|
||||
implementation: { kind: "yoi", display_hint: "Yoi" },
|
||||
workdir_attachments: [],
|
||||
diagnostics: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
await page.getByRole("button", { name: "Start Worker" }).click();
|
||||
const body = (await submitted).postDataJSON();
|
||||
assertEquals(body.workdir_attachments, []);
|
||||
assertEquals(body.ticket_assignment, null);
|
||||
assertEquals(body.runtime_id, "embedded");
|
||||
await page.waitForURL("**/w/home-empty");
|
||||
assertEquals(errors, []);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
} finally {
|
||||
assertEquals(await stopOwnedProcesses(processes), []);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "optional-repository-interactions",
|
||||
"title": "Optional repository disclosure and validation failure",
|
||||
"baseUrl": "http://127.0.0.1:15281",
|
||||
"colorScheme": "light",
|
||||
"reducedMotion": "reduce",
|
||||
"personas": [
|
||||
{
|
||||
"id": "fixture",
|
||||
"label": "Synthetic account; permissions vary by Workspace",
|
||||
"auth": {
|
||||
"kind": "anonymous"
|
||||
}
|
||||
}
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"label": "desktop",
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"label": "narrow",
|
||||
"width": 768,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"label": "mobile",
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"id": "catalog",
|
||||
"label": "Create Workspace without a repository",
|
||||
"path": "/",
|
||||
"goal": "Reopen a zero-repository Workspace or create a new Workspace without configuring Git.",
|
||||
"dataState": "Opt-in Git fields followed by intentional HTTP400 creation validation failure; no actual resources are created.",
|
||||
"ready": {
|
||||
"kind": "selector",
|
||||
"selector": ".workspace-create-panel input"
|
||||
},
|
||||
"capturePoints": [
|
||||
{
|
||||
"id": "repository-fields",
|
||||
"label": "Opt-in Git fields",
|
||||
"fullPage": true,
|
||||
"interaction": [
|
||||
{
|
||||
"action": "click",
|
||||
"selector": ".workspace-repository-toggle input"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "validation-error",
|
||||
"label": "Intentional Backend validation failure",
|
||||
"fullPage": true,
|
||||
"interaction": [
|
||||
{
|
||||
"action": "click",
|
||||
"selector": ".workspace-repository-toggle input"
|
||||
},
|
||||
{
|
||||
"action": "fill",
|
||||
"selector": ".workspace-create-panel input[required]",
|
||||
"value": "Rejected Workspace"
|
||||
},
|
||||
{
|
||||
"action": "click",
|
||||
"selector": ".workspace-create-panel button[type=submit]"
|
||||
}
|
||||
],
|
||||
"ready": {
|
||||
"kind": "selector",
|
||||
"selector": ".workspace-create-panel [role=alert]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"processes": [
|
||||
{
|
||||
"id": "workspace-fixture",
|
||||
"command": "deno",
|
||||
"cwd": "../../../../tools/web-ux",
|
||||
"args": [
|
||||
"run",
|
||||
"--allow-net",
|
||||
"--allow-read",
|
||||
"browser-tests/workspace_home_fixture_server.ts",
|
||||
"15282",
|
||||
"../../web/workspace/build"
|
||||
],
|
||||
"readyUrl": "http://127.0.0.1:15282/health"
|
||||
},
|
||||
{
|
||||
"id": "empty-workspace-api",
|
||||
"command": "deno",
|
||||
"cwd": "../..",
|
||||
"args": [
|
||||
"run",
|
||||
"--allow-net",
|
||||
"test/visual-review/empty-workspace-fixture-server.ts",
|
||||
"15281",
|
||||
"http://127.0.0.1:15282"
|
||||
],
|
||||
"readyUrl": "http://127.0.0.1:15281/health"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "optional-initial-repository",
|
||||
"title": "Optional initial repository and empty Workspace",
|
||||
"baseUrl": "http://127.0.0.1:15281",
|
||||
"colorScheme": "light",
|
||||
"reducedMotion": "reduce",
|
||||
"personas": [
|
||||
{
|
||||
"id": "fixture",
|
||||
"label": "Synthetic account; permissions vary by Workspace",
|
||||
"auth": {
|
||||
"kind": "anonymous"
|
||||
}
|
||||
}
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"label": "desktop",
|
||||
"width": 1440,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"label": "narrow",
|
||||
"width": 768,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"label": "mobile",
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"id": "catalog",
|
||||
"label": "Create Workspace without a repository",
|
||||
"path": "/",
|
||||
"goal": "Reopen a zero-repository Workspace or create a new Workspace without configuring Git.",
|
||||
"dataState": "Accessible Workspaces with no repositories and blank creation form.",
|
||||
"ready": {
|
||||
"kind": "selector",
|
||||
"selector": ".workspace-create-panel input"
|
||||
},
|
||||
"capturePoints": [
|
||||
{
|
||||
"id": "initial",
|
||||
"label": "Blank creation form",
|
||||
"fullPage": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "empty-owner",
|
||||
"label": "Empty Workspace owner",
|
||||
"path": "/w/home-empty",
|
||||
"goal": "Use Home and Sidebar without any repositories or work records.",
|
||||
"dataState": "Owner permissions; zero repositories, Workers, tickets, Objectives, and Merge Requests.",
|
||||
"ready": {
|
||||
"kind": "selector",
|
||||
"selector": ".workspace-home:has([data-feed-ready='home-attention']):has([data-feed-ready='home-active']):has([data-feed-ready='home-recent'])"
|
||||
},
|
||||
"capturePoints": [
|
||||
{
|
||||
"id": "initial",
|
||||
"label": "Empty Home and Sidebar"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "member",
|
||||
"label": "Zero-repository member",
|
||||
"path": "/w/home-member",
|
||||
"goal": "Navigate authorized work without owner-only repository management actions.",
|
||||
"dataState": "Non-owner permissions; zero repositories, representative work records.",
|
||||
"ready": {
|
||||
"kind": "selector",
|
||||
"selector": ".workspace-home:has([data-feed-ready='home-attention']):has([data-feed-ready='home-active']):has([data-feed-ready='home-recent'])"
|
||||
},
|
||||
"capturePoints": [
|
||||
{
|
||||
"id": "initial",
|
||||
"label": "Member Home and Sidebar"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "worker",
|
||||
"label": "New Worker without repositories",
|
||||
"path": "/w/home-empty/workers/new",
|
||||
"goal": "Start a normal conversation Worker without a Workdir on embedded Runtime.",
|
||||
"dataState": "No repositories or Workdirs, embedded and remote Runtime candidates.",
|
||||
"ready": {
|
||||
"kind": "selector",
|
||||
"selector": ".worker-launch-form"
|
||||
},
|
||||
"capturePoints": [
|
||||
{
|
||||
"id": "initial",
|
||||
"label": "Embedded Worker launch",
|
||||
"fullPage": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"processes": [
|
||||
{
|
||||
"id": "workspace-fixture",
|
||||
"command": "deno",
|
||||
"cwd": "../../../../tools/web-ux",
|
||||
"args": [
|
||||
"run",
|
||||
"--allow-net",
|
||||
"--allow-read",
|
||||
"browser-tests/workspace_home_fixture_server.ts",
|
||||
"15282",
|
||||
"../../web/workspace/build"
|
||||
],
|
||||
"readyUrl": "http://127.0.0.1:15282/health"
|
||||
},
|
||||
{
|
||||
"id": "empty-workspace-api",
|
||||
"command": "deno",
|
||||
"cwd": "../..",
|
||||
"args": [
|
||||
"run",
|
||||
"--allow-net",
|
||||
"test/visual-review/empty-workspace-fixture-server.ts",
|
||||
"15281",
|
||||
"http://127.0.0.1:15282"
|
||||
],
|
||||
"readyUrl": "http://127.0.0.1:15281/health"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user