Public: anyone can read this; the crew can change it.
A client's first screen leaves archived apps out #3486
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3479-a-client-s-first-screen-leaves-archived → main 1 commit fork
2 files
changed +29
−3
src/core/client-apps.ts +3 −3
| @@ -6,7 +6,7 @@ | ||
| 6 | 6 | |
| 7 | 7 | import type { App } from "../app.ts"; |
| 8 | 8 | import { spawnedRepos } from "./next-steps.ts"; |
| 9 | import type { Repo } from "./repos.ts"; | |
| 9 | import { withoutArchived, type Repo } from "./repos.ts"; | |
| 10 | 10 | import type { User } from "./users.ts"; |
| 11 | 11 | |
| 12 | 12 | export interface ClientApp { |
| @@ -17,12 +17,12 @@ export interface ClientApp { | ||
| 17 | 17 | readonly queued: number; |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | /** The apps `user` can see on a spawned forge, each with its agent counts. */ | |
| 20 | /** The apps `user` can see on a spawned forge, each with its agent counts; archived ones are put away, not shown. */ | |
| 21 | 21 | export async function clientAppRows( |
| 22 | 22 | app: App, |
| 23 | 23 | user: User, |
| 24 | 24 | ): Promise<readonly ClientApp[]> { |
| 25 | const repos = await spawnedRepos(app, user); | |
| 25 | const repos = withoutArchived(await spawnedRepos(app, user)); | |
| 26 | 26 | if (repos.length === 0) return []; |
| 27 | 27 | const rows = app.db |
| 28 | 28 | .query<{ repo_id: string; state: string; n: number }, string[]>( |
test/client-home.test.ts +26
| @@ -12,7 +12,9 @@ import { join } from "node:path"; | ||
| 12 | 12 | import { createRepo } from "../src/actions/repos.ts"; |
| 13 | 13 | import { childDirOf, spawnForge } from "../src/actions/spawn.ts"; |
| 14 | 14 | import { createApp, type App } from "../src/app.ts"; |
| 15 | import { clientAppRows } from "../src/core/client-apps.ts"; | |
| 15 | 16 | import { addCrew, createAgentRow, setCrewRole } from "../src/core/agents.ts"; |
| 17 | import { moveArchived } from "../src/core/repos.ts"; | |
| 16 | 18 | import { createSession } from "../src/core/sessions.ts"; |
| 17 | 19 | import { linkEveryday } from "../src/core/everyday.ts"; |
| 18 | 20 | import { createUser, getUserByName } from "../src/core/users.ts"; |
| @@ -291,4 +293,28 @@ describe("a client's first screen", () => { | ||
| 291 | 293 | expect(app.screen.toText()).not.toContain("Shipped per hour"); |
| 292 | 294 | app.stop(); |
| 293 | 295 | }); |
| 296 | ||
| 297 | test("an archived app leaves the first screen and comes back when woken", async () => { | |
| 298 | const maria = getUserByName(child.db, "maria")!; | |
| 299 | const second = await createRepo(child, maria, { name: "cafe", owner: "marias" }); | |
| 300 | if (!second.ok) throw new Error(JSON.stringify(second.error)); | |
| 301 | const bakery = child.db | |
| 302 | .query<{ id: string }, []>("SELECT id FROM repo WHERE name = 'bakery'") | |
| 303 | .get()!; | |
| 304 | const names = async () => | |
| 305 | (await clientAppRows(child, maria)).map((r) => r.repo.name).sort(); | |
| 306 | expect(await names()).toEqual(["bakery", "cafe"]); | |
| 307 | expect(moveArchived(child.db, bakery.id, true)).toBe(true); | |
| 308 | expect(await names()).toEqual(["cafe"]); | |
| 309 | const apps = async () => { | |
| 310 | const home = main(await page(owner)); | |
| 311 | return home.slice(home.indexOf('id="client-apps"')); | |
| 312 | }; | |
| 313 | expect(await apps()).not.toContain('href="/marias/bakery"'); | |
| 314 | expect(await apps()).toContain('href="/marias/cafe"'); | |
| 315 | expect((await fetch(url("/marias/bakery"), { headers: { cookie: owner } })).status).toBe(200); | |
| 316 | expect(moveArchived(child.db, bakery.id, false)).toBe(true); | |
| 317 | expect(await names()).toEqual(["bakery", "cafe"]); | |
| 318 | expect(await apps()).toContain('href="/marias/bakery"'); | |
| 319 | }); | |
| 294 | 320 | }); |
new above
offline