Public: anyone can read this; the crew can change it.
After New app, land on the studio's app page — the one the studio built #161
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/151-after-new-app-land-on-the-studio-s-app-p → main 1 commit fork
3 files
changed +41
−11
src/web/pages/apps.ts +4 −1
| @@ -531,7 +531,10 @@ export async function newAppSubmit(ctx: Ctx): Promise<Response> { | ||
| 531 | 531 | : plan |
| 532 | 532 | ? ` ${plan.agent} was picked for #${issue.number} — ${plan.summary}` |
| 533 | 533 | : ` #${issue.number} is assigned to ${issue.assigneeName}.`; |
| 534 | return flashTo(ctx, `${repoBase(repo)}/app`, { message: `${first}${second}` }); | |
| 534 | // The studio's app page (repoHome, once the record exists) is where this | |
| 535 | // moment belongs; dev mode keeps the controls' page. | |
| 536 | const target = ctx.mode === "studio" ? repoBase(repo) : `${repoBase(repo)}/app`; | |
| 537 | return flashTo(ctx, target, { message: `${first}${second}` }); | |
| 535 | 538 | } |
| 536 | 539 | |
| 537 | 540 | // ---- the repo home's strip ---- |
test/apps.test.ts +34 −6
| @@ -1280,7 +1280,7 @@ describe("New app: the form asks for the sentence, and names it (issue #60)", () | ||
| 1280 | 1280 | }); |
| 1281 | 1281 | expect(res.status).toBe(303); |
| 1282 | 1282 | expect(res.headers.get("location")).toBe( |
| 1283 | "/nadia/keep-a-shared-grocery-list/app", | |
| 1283 | "/nadia/keep-a-shared-grocery-list", | |
| 1284 | 1284 | ); |
| 1285 | 1285 | }); |
| 1286 | 1286 | |
| @@ -1290,7 +1290,7 @@ describe("New app: the form asks for the sentence, and names it (issue #60)", () | ||
| 1290 | 1290 | }); |
| 1291 | 1291 | expect(res.status).toBe(303); |
| 1292 | 1292 | expect(res.headers.get("location")).toBe( |
| 1293 | "/nadia/keep-a-shared-grocery-list-2/app", | |
| 1293 | "/nadia/keep-a-shared-grocery-list-2", | |
| 1294 | 1294 | ); |
| 1295 | 1295 | }); |
| 1296 | 1296 | |
| @@ -1308,7 +1308,7 @@ describe("New app: the form asks for the sentence, and names it (issue #60)", () | ||
| 1308 | 1308 | description: "Keep a shared grocery list", |
| 1309 | 1309 | }); |
| 1310 | 1310 | expect(res.status).toBe(303); |
| 1311 | expect(res.headers.get("location")).toBe("/nadia/groceries/app"); | |
| 1311 | expect(res.headers.get("location")).toBe("/nadia/groceries"); | |
| 1312 | 1312 | }); |
| 1313 | 1313 | }); |
| 1314 | 1314 | |
| @@ -1367,7 +1367,7 @@ describe("New app lands on the app's page, not the flash (issue #133)", () => { | ||
| 1367 | 1367 | await rm(dataDir, { recursive: true, force: true }); |
| 1368 | 1368 | }); |
| 1369 | 1369 | |
| 1370 | test("POST /apps/new redirects to the app's page, which shows the name, the URL and the first issue", async () => { | |
| 1370 | test("in studio mode, POST /apps/new redirects to the studio's app page, which shows the name, the URL and the first issue", async () => { | |
| 1371 | 1371 | const submitted = await fetch(`${base}/apps/new`, { |
| 1372 | 1372 | method: "POST", |
| 1373 | 1373 | headers: { cookie, origin: base }, |
| @@ -1381,7 +1381,7 @@ describe("New app lands on the app's page, not the flash (issue #133)", () => { | ||
| 1381 | 1381 | redirect: "manual", |
| 1382 | 1382 | }); |
| 1383 | 1383 | expect(submitted.status).toBe(303); |
| 1384 | expect(submitted.headers.get("location")).toBe("/priya/planner/app"); | |
| 1384 | expect(submitted.headers.get("location")).toBe("/priya/planner"); | |
| 1385 | 1385 | const flash = (submitted.headers.get("set-cookie") ?? "").split(";")[0]!; |
| 1386 | 1386 | expect(flash).toStartWith("forge_flash="); |
| 1387 | 1387 | |
| @@ -1391,7 +1391,35 @@ describe("New app lands on the app's page, not the flash (issue #133)", () => { | ||
| 1391 | 1391 | expect(landed.status).toBe(200); |
| 1392 | 1392 | const html = await landed.text(); |
| 1393 | 1393 | expect(html).toContain("planner"); |
| 1394 | expect(html).toContain(`http://planner.${DOMAIN}:${servers.port}`); | |
| 1394 | expect(html).toContain(`planner.${DOMAIN}:${servers.port}`); | |
| 1395 | expect(html).toContain("#1 is assigned to bot."); | |
| 1396 | }); | |
| 1397 | ||
| 1398 | test("in dev mode, POST /apps/new redirects to dev's app page, chosen from ctx.mode", async () => { | |
| 1399 | const submitted = await fetch(`${base}/apps/new`, { | |
| 1400 | method: "POST", | |
| 1401 | headers: { cookie: `${cookie}; forge_mode=dev`, origin: base }, | |
| 1402 | body: new URLSearchParams({ | |
| 1403 | owner: "priya", | |
| 1404 | name: "tracker", | |
| 1405 | description: "Track expenses", | |
| 1406 | visibility: "private", | |
| 1407 | agent: "bot", | |
| 1408 | }), | |
| 1409 | redirect: "manual", | |
| 1410 | }); | |
| 1411 | expect(submitted.status).toBe(303); | |
| 1412 | expect(submitted.headers.get("location")).toBe("/priya/tracker/app"); | |
| 1413 | const flash = (submitted.headers.get("set-cookie") ?? "").split(";")[0]!; | |
| 1414 | expect(flash).toStartWith("forge_flash="); | |
| 1415 | ||
| 1416 | const landed = await fetch(`${base}${submitted.headers.get("location")}`, { | |
| 1417 | headers: { cookie: `${cookie}; forge_mode=dev; ${flash}` }, | |
| 1418 | }); | |
| 1419 | expect(landed.status).toBe(200); | |
| 1420 | const html = await landed.text(); | |
| 1421 | expect(html).toContain("tracker"); | |
| 1422 | expect(html).toContain(`http://tracker.${DOMAIN}:${servers.port}`); | |
| 1395 | 1423 | expect(html).toContain("#1 is assigned to bot."); |
| 1396 | 1424 | }); |
| 1397 | 1425 |
test/web-apps.test.ts +3 −4
| @@ -164,7 +164,7 @@ describe("apps, runs and the crew on the web", () => { | ||
| 164 | 164 | }, |
| 165 | 165 | }); |
| 166 | 166 | expect(created.status).toBe(303); |
| 167 | expect(created.headers.get("location")).toBe("/alice/hello/app"); | |
| 167 | expect(created.headers.get("location")).toBe("/alice/hello"); | |
| 168 | 168 | expect(flashOf(created)).toContain( |
| 169 | 169 | `hello is starting at http://hello.${DOMAIN}:${servers.port}`, |
| 170 | 170 | ); |
| @@ -172,11 +172,10 @@ describe("apps, runs and the crew on the web", () => { | ||
| 172 | 172 | expect(hello).toBeDefined(); |
| 173 | 173 | expect(getAppByRepo(app.db, hello.id)?.slug).toBe("hello"); |
| 174 | 174 | |
| 175 | // The repo home wears the strip and the App tab. | |
| 175 | // The studio's app page — the repo home for a repository that is an | |
| 176 | // app — carries the same URL, in a link to open it. | |
| 176 | 177 | const home = await bodyOf(alice, "/alice/hello"); |
| 177 | expect(home).toContain('class="app-strip"'); | |
| 178 | 178 | expect(home).toContain('href="/alice/hello/app/production/open"'); |
| 179 | expect(home).toContain('href="/alice/hello/app"'); | |
| 180 | 179 | // A plain repository wears neither. |
| 181 | 180 | const plain = await web(alice, "/new", { |
| 182 | 181 | form: { owner: "alice", name: "plain", visibility: "private" }, |
new above
offline