Public: anyone can read this; the crew can change it.

The preview seed holds a wave and a milestone, so the Story pages are seen filled on every preview #8892

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/8868-the-preview-seed-holds-a-wave-and-a-mile → main 2 commits fork

  • Merged. mason/8868-the-preview-seed-holds-a-wave-and-a-mile was merged into main as 23b4bec.

Review

Sign in to review this change.
4 files changed +68 −5
forge.json +2 −2
@@ -3,8 +3,8 @@
33 "api.anthropic.com"
44 ],
55 "app": {
6 "looks": ["/", "/forges", "/family", "/front", "/explore", "/board", "/notes", "/feed", "/review", "/new", "/audit", "/releases", "/apps", "/repos", "/map", "/system", "/talk", "/flow", "/resilience", "/settings", "/preview-admin/sample", "/preview-admin/sample/pulls/3", "/preview-admin/sample/next", "/preview-admin/sample/bets"],
7 "looksAsOwner": ["/", "/flow", "/talk", "/system", "/resilience", "/preview-admin/sample", "/preview-admin/sample/pulls/3", "/preview-admin/sample/next", "/preview-admin/sample/bets"],
6 "looks": ["/", "/front", "/explore", "/board", "/notes", "/feed", "/review", "/new", "/audit", "/releases", "/apps", "/repos", "/map", "/system", "/talk", "/flow", "/resilience", "/settings", "/preview-admin/sample", "/preview-admin/sample/pulls/3", "/preview-admin/sample/next", "/preview-admin/sample/bets", "/preview-admin/sample/waves", "/preview-admin/sample/milestones"],
7 "looksAsOwner": ["/", "/flow", "/talk", "/system", "/resilience", "/preview-admin/sample", "/preview-admin/sample/pulls/3", "/preview-admin/sample/next", "/preview-admin/sample/bets", "/preview-admin/sample/waves", "/preview-admin/sample/milestones"],
88 "run": "bun run src/main.ts",
99 "install": "",
1010 "health": "/healthz",
src/spawn/preview-seed.ts +52 −1
@@ -25,6 +25,7 @@ import { openPull } from "../actions/pulls.ts";
2525 import { commitToBranch, createRepo } from "../actions/repos.ts";
2626 import { turnValveFor } from "../actions/valves.ts";
2727 import { insertBet } from "../core/bets.ts";
28 import { addDependency } from "../core/dependencies.ts";
2829 import { claimRun, createRun, finishRun } from "../core/runs.ts";
2930 import { noteLimit } from "../core/standing.ts";
3031 import { addSubscription } from "../core/subscriptions.ts";
@@ -43,6 +44,9 @@ export const PREVIEW_REPO = "sample";
4344 * other number rather than leave that look on a 404.
4445 */
4546 export const PREVIEW_PULL = 3;
47 /** The seeded wave's version and headline, which the owner's /waves look shows. */
48 export const PREVIEW_WAVE = "0.1.0";
49 export const PREVIEW_WAVE_HEADLINE = "The sample says hello";
4650 /** The board whose panels are pinned to home. */
4751 const PREVIEW_BOARD = "preview";
4852 const SEEDED = "preview_seeded";
@@ -128,6 +132,7 @@ async function seedWorld(app: App, admin: User): Promise<Result<void, string>> {
128132 ].map((input) => openIssue(app, admin, repo.value, input));
129133 const failed = issues.find((i) => !i.ok);
130134 if (failed !== undefined && !failed.ok) return err(`an issue: ${String(failed.error)}`);
135 const [milestone, held] = issues.flatMap((i) => (i.ok ? [i.value] : []));
131136 const pull = await openPull(app, admin, repo.value, {
132137 title: "Say hello",
133138 body: "Adds a greeting.",
@@ -166,6 +171,10 @@ async function seedWorld(app: App, admin: User): Promise<Result<void, string>> {
166171 }
167172 const priced = seedPrices(app, admin, repo.value);
168173 if (!priced.ok) return priced;
174 const milestoned = seedMilestone(app, milestone!, [held!, ...priced.value.slice(0, 2)]);
175 if (!milestoned.ok) return milestoned;
176 const waved = await seedWave(app, admin, repo.value, pull.value.title);
177 if (!waved.ok) return waved;
169178 const bets = seedBets(app, admin, repo.value, open);
170179 if (!bets.ok) return bets;
171180 const valve = turnValveFor(app, admin, `${PREVIEW_ADMIN}/${PREVIEW_REPO}`, 60);
@@ -193,7 +202,8 @@ async function seedWorld(app: App, admin: User): Promise<Result<void, string>> {
193202 * granted to no repository or owner, so `pickSubscription` (which joins
194203 * on grants) never offers it to a run or a conversation.
195204 */
196 function seedPrices(app: App, admin: User, repo: Repo): Result<void, string> {
205 function seedPrices(app: App, admin: User, repo: Repo): Result<Issue[], string> {
206 const finished: Issue[] = [];
197207 for (const [at, usd] of [1.5, 2, 2.5, 3].entries()) {
198208 const made = openIssue(app, admin, repo, {
199209 title: `Done work ${at + 1}`,
@@ -211,6 +221,7 @@ function seedPrices(app: App, admin: User, repo: Repo): Result<void, string> {
211221 finishRun(app.db, run.id, { state: "ok", summary: "A seeded run", costUsd: usd });
212222 const closed = setIssueState(app, admin, repo, made.value, "closed", { reason: "done" });
213223 if (!closed.ok) return err(`closing a finished issue: ${String(closed.error)}`);
224 finished.push(made.value);
214225 }
215226 const added = addSubscription(app.db, app.config.dataDir, {
216227 name: "preview sample",
@@ -245,9 +256,49 @@ function seedPrices(app: App, admin: User, repo: Repo): Result<void, string> {
245256 said: "weekly limit",
246257 });
247258 spend(1, new Date(now - DAY));
259 return ok(finished);
260 }
261
262 /**
263 * One milestone for /milestones: the first issue waits for three others,
264 * two finished and one open, so it reads as two of three done.
265 */
266 function seedMilestone(app: App, milestone: Issue, holding: readonly Issue[]): Result<void, string> {
267 for (const blocker of holding) {
268 const linked = addDependency(app.db, milestone, blocker, undefined);
269 if (!linked.ok) return err(`a milestone's dependency: ${linked.error}`);
270 }
248271 return ok();
249272 }
250273
274 /** The wave for /waves: `waves/<version>.md` on the default branch, naming the seeded pull. */
275 async function seedWave(app: App, admin: User, repo: Repo, pullTitle: string): Promise<Result<void, string>> {
276 const url = `https://preview.invalid/${PREVIEW_ADMIN}/${PREVIEW_REPO}/pulls/${String(PREVIEW_PULL)}`;
277 const text = [
278 "---",
279 `version: ${PREVIEW_WAVE}`,
280 `date: ${new Date().toISOString().slice(0, 10)}`,
281 `headline: ${PREVIEW_WAVE_HEADLINE}`,
282 "---",
283 "",
284 "## Highlights",
285 "",
286 "- **A greeting** — So the sample says hello.",
287 "- **A week to see** — So /flow has pipes to draw.",
288 "- **A plan to read** — So milestones move as issues close.",
289 "",
290 "## Pulls",
291 "",
292 `- [#${String(PREVIEW_PULL)} ${pullTitle}](${url})`,
293 "",
294 ].join("\n");
295 const done = await commitToBranch(app, admin, repo, {
296 message: `Cut wave ${PREVIEW_WAVE}`,
297 files: [{ path: `waves/${PREVIEW_WAVE}.md`, content: text }],
298 });
299 return done.ok ? ok() : err(`the wave: ${JSON.stringify(done.error)}`);
300 }
301
251302 /** One bet backed at 20% of the week and one proposed, over the open issues. */
252303 function seedBets(app: App, admin: User, repo: Repo, open: readonly Issue[]): Result<void, string> {
253304 const bet = (name: string, from: number) =>
test/forge-looks.test.ts +2
@@ -22,6 +22,8 @@ quietLogs();
2222 const NOT_A_PAGE: Record<string, string> = {
2323 "/deploy-to": "a form for a forge admin that fills from its link; covered by test/pinned-page.test.ts",
2424 "/asked": "no picture: the cap of 24 looks went to /notes, the page that now leads with a person's thoughts; /asked is covered by test/asked.test.ts",
25 "/forges": "no picture: the cap of 24 looks went to the preview's /waves and /milestones; the page is covered by its own tests",
26 "/family": "no picture: the cap of 24 looks went to the preview's /waves and /milestones; the page is covered by its own tests",
2527 "/llms.txt": "plain markdown for machines, not a page to picture",
2628 "/robots.txt": "plain text for crawlers",
2729 "/openapi.json": "a redirect to the contract document",
test/looks-owner.test.ts +12 −2
@@ -34,7 +34,7 @@ import { ok } from "../src/kernel/result.ts";
3434 import type { Capture, LookInput } from "../src/looks/capture.ts";
3535 import { startLooks, type LooksHandle } from "../src/looks/service.ts";
3636 import { previewIdentityKey } from "../src/spawn/preview-owner.ts";
37 import { PREVIEW_ADMIN, PREVIEW_PULL, PREVIEW_REPO, seedPreview } from "../src/spawn/preview-seed.ts";
37 import { PREVIEW_ADMIN, PREVIEW_PULL, PREVIEW_REPO, PREVIEW_WAVE, PREVIEW_WAVE_HEADLINE, seedPreview } from "../src/spawn/preview-seed.ts";
3838 import { quietLogs } from "./helpers/globals.ts";
3939
4040 quietLogs();
@@ -270,7 +270,7 @@ describe("the forge's own owner looks, over the preview seed", () => {
270270 test("forge.json takes home, /flow, the seeded repository and its pull as the owner, desk and phone", () => {
271271 const manifest = parseManifest(readFileSync(join(import.meta.dir, "..", "forge.json"), "utf8"), undefined);
272272 if (!manifest.ok) throw new Error(JSON.stringify(manifest.error));
273 for (const page of ["/", "/flow", repoHome, pullPage]) {
273 for (const page of ["/", "/flow", repoHome, pullPage, `${repoHome}/waves`, `${repoHome}/milestones`]) {
274274 expect(manifest.value.looks).toContain(page);
275275 expect(manifest.value.looksAsOwner).toContain(page);
276276 }
@@ -295,6 +295,16 @@ describe("the forge's own owner looks, over the preview seed", () => {
295295 expect(page).toContain(`href="${repoHome}">${PREVIEW_REPO}`);
296296 });
297297
298 test("/waves holds the wave and /milestones a milestone that is two of three done", async () => {
299 const waves = await asOwner(`${repoHome}/waves`);
300 expect(waves).toContain(PREVIEW_WAVE);
301 expect(waves).toContain(PREVIEW_WAVE_HEADLINE);
302 expect(waves).toContain("1 pull");
303 const milestones = await asOwner(`${repoHome}/milestones`);
304 expect(milestones).toContain("The home page says hello");
305 expect(milestones).toContain("2 of 3 done");
306 });
307
298308 test("the pull's page carries the Ship decision", async () => {
299309 const page = await asOwner(pullPage);
300310 const decide = page.indexOf('id="decide"');

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs