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
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/8868-the-preview-seed-holds-a-wave-and-a-mile → main 2 commits fork
4 files
changed +68
−5
forge.json +2 −2
| @@ -3,8 +3,8 @@ | ||
| 3 | 3 | "api.anthropic.com" |
| 4 | 4 | ], |
| 5 | 5 | "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"], | |
| 8 | 8 | "run": "bun run src/main.ts", |
| 9 | 9 | "install": "", |
| 10 | 10 | "health": "/healthz", |
src/spawn/preview-seed.ts +52 −1
| @@ -25,6 +25,7 @@ import { openPull } from "../actions/pulls.ts"; | ||
| 25 | 25 | import { commitToBranch, createRepo } from "../actions/repos.ts"; |
| 26 | 26 | import { turnValveFor } from "../actions/valves.ts"; |
| 27 | 27 | import { insertBet } from "../core/bets.ts"; |
| 28 | import { addDependency } from "../core/dependencies.ts"; | |
| 28 | 29 | import { claimRun, createRun, finishRun } from "../core/runs.ts"; |
| 29 | 30 | import { noteLimit } from "../core/standing.ts"; |
| 30 | 31 | import { addSubscription } from "../core/subscriptions.ts"; |
| @@ -43,6 +44,9 @@ export const PREVIEW_REPO = "sample"; | ||
| 43 | 44 | * other number rather than leave that look on a 404. |
| 44 | 45 | */ |
| 45 | 46 | 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"; | |
| 46 | 50 | /** The board whose panels are pinned to home. */ |
| 47 | 51 | const PREVIEW_BOARD = "preview"; |
| 48 | 52 | const SEEDED = "preview_seeded"; |
| @@ -128,6 +132,7 @@ async function seedWorld(app: App, admin: User): Promise<Result<void, string>> { | ||
| 128 | 132 | ].map((input) => openIssue(app, admin, repo.value, input)); |
| 129 | 133 | const failed = issues.find((i) => !i.ok); |
| 130 | 134 | if (failed !== undefined && !failed.ok) return err(`an issue: ${String(failed.error)}`); |
| 135 | const [milestone, held] = issues.flatMap((i) => (i.ok ? [i.value] : [])); | |
| 131 | 136 | const pull = await openPull(app, admin, repo.value, { |
| 132 | 137 | title: "Say hello", |
| 133 | 138 | body: "Adds a greeting.", |
| @@ -166,6 +171,10 @@ async function seedWorld(app: App, admin: User): Promise<Result<void, string>> { | ||
| 166 | 171 | } |
| 167 | 172 | const priced = seedPrices(app, admin, repo.value); |
| 168 | 173 | 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; | |
| 169 | 178 | const bets = seedBets(app, admin, repo.value, open); |
| 170 | 179 | if (!bets.ok) return bets; |
| 171 | 180 | 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>> { | ||
| 193 | 202 | * granted to no repository or owner, so `pickSubscription` (which joins |
| 194 | 203 | * on grants) never offers it to a run or a conversation. |
| 195 | 204 | */ |
| 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[] = []; | |
| 197 | 207 | for (const [at, usd] of [1.5, 2, 2.5, 3].entries()) { |
| 198 | 208 | const made = openIssue(app, admin, repo, { |
| 199 | 209 | title: `Done work ${at + 1}`, |
| @@ -211,6 +221,7 @@ function seedPrices(app: App, admin: User, repo: Repo): Result<void, string> { | ||
| 211 | 221 | finishRun(app.db, run.id, { state: "ok", summary: "A seeded run", costUsd: usd }); |
| 212 | 222 | const closed = setIssueState(app, admin, repo, made.value, "closed", { reason: "done" }); |
| 213 | 223 | if (!closed.ok) return err(`closing a finished issue: ${String(closed.error)}`); |
| 224 | finished.push(made.value); | |
| 214 | 225 | } |
| 215 | 226 | const added = addSubscription(app.db, app.config.dataDir, { |
| 216 | 227 | name: "preview sample", |
| @@ -245,9 +256,49 @@ function seedPrices(app: App, admin: User, repo: Repo): Result<void, string> { | ||
| 245 | 256 | said: "weekly limit", |
| 246 | 257 | }); |
| 247 | 258 | 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 | } | |
| 248 | 271 | return ok(); |
| 249 | 272 | } |
| 250 | 273 | |
| 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 | ||
| 251 | 302 | /** One bet backed at 20% of the week and one proposed, over the open issues. */ |
| 252 | 303 | function seedBets(app: App, admin: User, repo: Repo, open: readonly Issue[]): Result<void, string> { |
| 253 | 304 | const bet = (name: string, from: number) => |
test/forge-looks.test.ts +2
| @@ -22,6 +22,8 @@ quietLogs(); | ||
| 22 | 22 | const NOT_A_PAGE: Record<string, string> = { |
| 23 | 23 | "/deploy-to": "a form for a forge admin that fills from its link; covered by test/pinned-page.test.ts", |
| 24 | 24 | "/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", | |
| 25 | 27 | "/llms.txt": "plain markdown for machines, not a page to picture", |
| 26 | 28 | "/robots.txt": "plain text for crawlers", |
| 27 | 29 | "/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"; | ||
| 34 | 34 | import type { Capture, LookInput } from "../src/looks/capture.ts"; |
| 35 | 35 | import { startLooks, type LooksHandle } from "../src/looks/service.ts"; |
| 36 | 36 | 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"; | |
| 38 | 38 | import { quietLogs } from "./helpers/globals.ts"; |
| 39 | 39 | |
| 40 | 40 | quietLogs(); |
| @@ -270,7 +270,7 @@ describe("the forge's own owner looks, over the preview seed", () => { | ||
| 270 | 270 | test("forge.json takes home, /flow, the seeded repository and its pull as the owner, desk and phone", () => { |
| 271 | 271 | const manifest = parseManifest(readFileSync(join(import.meta.dir, "..", "forge.json"), "utf8"), undefined); |
| 272 | 272 | 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`]) { | |
| 274 | 274 | expect(manifest.value.looks).toContain(page); |
| 275 | 275 | expect(manifest.value.looksAsOwner).toContain(page); |
| 276 | 276 | } |
| @@ -295,6 +295,16 @@ describe("the forge's own owner looks, over the preview seed", () => { | ||
| 295 | 295 | expect(page).toContain(`href="${repoHome}">${PREVIEW_REPO}`); |
| 296 | 296 | }); |
| 297 | 297 | |
| 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 | ||
| 298 | 308 | test("the pull's page carries the Ship decision", async () => { |
| 299 | 309 | const page = await asOwner(pullPage); |
| 300 | 310 | const decide = page.indexOf('id="decide"'); |
new above
offline