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

realtime #16

Merged opened this in answer to

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

Merged

scout/forge:scout/6-realtime → main 1 commit fork

  • Merged. scout/6-realtime was merged into main as 7a199de.

Review

Sign in to review this change.
5 files changed +88 −5
src/web/pages/branches.ts +4 −1
@@ -26,6 +26,7 @@ import { loadRepo, notFoundPage, scopeHeader, signInFirst } from "../chrome.ts";
2626 import { field as formValue, readForm } from "../form.ts";
2727 import { html, when, type Html } from "../html.ts";
2828 import { flashTo, page } from "../layout.ts";
29 import { liveClientJs, liveMount } from "../live.ts";
2930 import { localScope } from "../scope.ts";
3031 import {
3132 badge,
@@ -277,7 +278,8 @@ export async function renderBranches(
277278 )}`,
278279 },
279280 )}
280 </div>`;
281 </div>
282 ${when(scope.kind === "local", () => liveMount({ repo: scope.base }))}`;
281283
282284 return page({
283285 ctx,
@@ -285,6 +287,7 @@ export async function renderBranches(
285287 crumbs: scope.crumbs,
286288 palette: scope.palette,
287289 body,
290 ...(scope.kind === "local" ? { scriptJs: liveClientJs } : {}),
288291 });
289292 }
290293
src/web/pages/commits.ts +6 −2
@@ -21,6 +21,7 @@ import { diffSyntax } from "../diff-syntax.ts";
2121 import { renderDiff, type SyntaxMap } from "../diff-view.ts";
2222 import { html, when, type Html } from "../html.ts";
2323 import { page } from "../layout.ts";
24 import { liveClientJs, liveMount } from "../live.ts";
2425 import { localScope, type RepoScope } from "../scope.ts";
2526 import {
2627 avatar,
@@ -210,7 +211,8 @@ export async function commitsPage(ctx: Ctx): Promise<Response> {
210211 crumbs: scope.crumbs,
211212 palette: scope.palette,
212213 body: html`${scopeHeader(ctx, scope, "commits")}${switcher}
213 ${noCommits(num > 1)}`,
214 ${noCommits(num > 1)} ${liveMount({ repo: base })}`,
215 scriptJs: liveClientJs,
214216 });
215217 }
216218
@@ -223,13 +225,15 @@ export async function commitsPage(ctx: Ctx): Promise<Response> {
223225 ...(num > 1 ? { prev: query(num - 1) } : {}),
224226 ...(hasMore ? { next: query(num + 1) } : {}),
225227 note: `Page ${num}`,
226 })}`;
228 })}
229 ${liveMount({ repo: base })}`;
227230 return page({
228231 ctx,
229232 title: `Commits · ${repo.name}`,
230233 crumbs: scope.crumbs,
231234 palette: scope.palette,
232235 body,
236 scriptJs: liveClientJs,
233237 });
234238 }
235239
src/web/pages/repo.ts +3 −2
@@ -36,6 +36,7 @@ import {
3636 } from "../chrome.ts";
3737 import { codeJs } from "../client.ts";
3838 import { highlightBlob } from "../code.ts";
39 import { liveClientJs, liveMount } from "../live.ts";
3940 import { appStrip } from "./apps.ts";
4041 import { studioApp } from "./studio.ts";
4142 import { getAppByRepo } from "../../core/apps.ts";
@@ -916,14 +917,14 @@ export async function repoHome(ctx: Ctx): Promise<Response> {
916917 entries: tree,
917918 rail: aboutCard(ctx, repo, refs, drift),
918919 })}
919 ${codeMount(scope, ref)} ${finderDialog()}`;
920 ${codeMount(scope, ref)} ${finderDialog()} ${liveMount({ repo: scope.base })}`;
920921 return page({
921922 ctx,
922923 title: repo.name,
923924 crumbs: scope.crumbs,
924925 palette: scope.palette,
925926 body,
926 scriptJs: codeJs,
927 scriptJs: `${codeJs}\n${liveClientJs}`,
927928 });
928929 }
929930
test/live-pages.test.ts added +73
@@ -0,0 +1,73 @@
1 /**
2 * The browsing pages — repo home, commit history, branches — wire up the
3 * same live client the issue/pull threads already had, so a push or a new
4 * branch shows up as a "new activity" pill without a reload. Verifies the
5 * wiring is present on a local repo and absent where there is nothing to
6 * subscribe to (an empty repo, or a repo read live off another forge).
7 */
8
9 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
10 import { mkdtemp, rm } from "node:fs/promises";
11 import { tmpdir } from "node:os";
12 import { join } from "node:path";
13 import { editFiles } from "../src/actions/contents.ts";
14 import { createApp, type App } from "../src/app.ts";
15 import { createRepo } from "../src/core/repos.ts";
16 import { createUser } from "../src/core/users.ts";
17 import { serve, type Servers } from "../src/http/server.ts";
18
19 describe("live browsing pages", () => {
20 let dataDir: string;
21 let app: App;
22 let servers: Servers;
23 let base: string;
24
25 beforeAll(async () => {
26 dataDir = await mkdtemp(join(tmpdir(), "forge-live-pages-"));
27 app = createApp({ dataDir, port: 0 });
28 servers = await serve(app);
29 base = servers.url;
30 const user = await createUser(app.db, {
31 username: "lex",
32 email: "[email protected]",
33 password: "live-pages-pass",
34 });
35 if (!user.ok) throw new Error("user");
36 const repo = await createRepo(app.db, app.reposRoot, {
37 ownerId: user.value.id,
38 name: "proj",
39 visibility: "public",
40 });
41 if (!repo.ok) throw new Error("repo");
42 const committed = await editFiles(app, user.value, repo.value, {
43 files: [{ path: "README.md", content: "# proj\n" }],
44 message: "first commit",
45 });
46 if (!committed.ok) throw new Error("commit");
47 });
48
49 afterAll(async () => {
50 servers.stop();
51 app.close();
52 await rm(dataDir, { recursive: true, force: true });
53 });
54
55 test("the repo home wires up the live client", async () => {
56 const html = await (await fetch(`${base}/lex/proj`)).text();
57 expect(html).toContain('id="live"');
58 expect(html).toContain('data-repo="/lex/proj"');
59 expect(html).toContain("new EventSource");
60 });
61
62 test("the commit history wires up the live client", async () => {
63 const html = await (await fetch(`${base}/lex/proj/commits`)).text();
64 expect(html).toContain('id="live"');
65 expect(html).toContain("new EventSource");
66 });
67
68 test("the branches page wires up the live client", async () => {
69 const html = await (await fetch(`${base}/lex/proj/branches`)).text();
70 expect(html).toContain('id="live"');
71 expect(html).toContain("new EventSource");
72 });
73 });
test/web-browse.test.ts +2
@@ -365,6 +365,8 @@ describe("browsing an origin repository on the web", () => {
365365 expect(html).not.toContain("/branches/main/delete");
366366 // The banner still says where the bytes came from.
367367 expect(html).toContain("Reading live from");
368 // A live-browse origin has no local repo to open an SSE stream against.
369 expect(html).not.toContain("new EventSource");
368370 });
369371
370372 test("commits and one commit render live, with the origin's author", async () => {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs