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

The studio's app and change pages wear a git chip no studio reader can act on: ● workbench · ○ this machine #317

Merged opened this

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

Merged

scout/forge:scout/281-the-studio-s-app-and-change-pages-wear-a → main 1 commit fork

  • Merged. scout/281-the-studio-s-app-and-change-pages-wear-a was merged into main as a7458af.

Review

Sign in to review this change.
4 files changed +73 −3
src/web/chrome.ts +3 −1
@@ -20,6 +20,7 @@ import {
2020 browseActions,
2121 hostOf,
2222 localScope,
23 studioOriginNote,
2324 upstreamHref,
2425 whereRow,
2526 type RepoScope,
@@ -281,7 +282,8 @@ export function scopeHeader(
281282 <span class="mono">${scope.upstream}</span>
282283 </p>`,
283284 )}
284 ${whereRow(ctx, scope)} ${tabs(items, "Repository")}
285 ${studio ? studioOriginNote(repo) : whereRow(ctx, scope)}
286 ${tabs(items, "Repository")}
285287 </header>`;
286288 }
287289
src/web/scope.ts +25
@@ -475,6 +475,31 @@ export function whereRow(ctx: Ctx, scope: RepoScope): Html {
475475 </div>`;
476476 }
477477
478 /**
479 * The studio's own answer to "where does this app's bytes live", for a
480 * repository the studio owns end to end. A native app is the studio's alone
481 * — there is nothing to say, so a caller in studio mode draws no strip at
482 * all rather than dev's three-place diagram of a git remote nobody here
483 * reads dev's words for. A mirrored app still owes a reader the one fact
484 * the strip carried — where it reads from, and how fresh — said as a
485 * sentence instead of dots.
486 */
487 export function studioOriginNote(repo: Repo | undefined): Html | null {
488 const at = repo?.origin;
489 if (at == null) return null;
490 const host = at.htmlUrl === "" ? at.fullName : hostOf(at.htmlUrl);
491 return html`<p class="repo-lineage">
492 Reads from
493 ${at.htmlUrl === ""
494 ? host
495 : html`<a href="${at.htmlUrl}" rel="noreferrer noopener" target="_blank"
496 >${host}</a
497 >`}${at.lastFetchAt === null
498 ? ""
499 : html`, synced ${timeAgo(at.lastFetchAt)}`}
500 </p>`;
501 }
502
478503 /**
479504 * The banner a live browse carries above its content. Deliberately quiet and
480505 * deliberately unmissable: it says what you are looking at, that you cannot
test/studio-words.test.ts +16 −2
@@ -93,6 +93,16 @@ const DEV_WORDS: readonly { pattern: RegExp; because: string }[] = [
9393 pattern: /Close issue\b/,
9494 because: "the studio's own word for a tell is tell",
9595 },
96 {
97 pattern: /\bworkbench\b/i,
98 because:
99 "#281: a native app is the studio's alone, so its header draws no where strip at all",
100 },
101 {
102 pattern: /this machine/i,
103 because:
104 "#281: dev's git chip (workbench · this machine) never reaches a studio header",
105 },
96106 {
97107 // Scoped to a badge's own content — "failed" also rides inside the
98108 // legitimate `serving · tests failed` phrase (`envPhrase`), which does
@@ -505,7 +515,10 @@ describe("dev's vocabulary never reaches a studio page", () => {
505515 path: repoPath,
506516 title: "vocab",
507517 shows: [`href="#i-pull"/></svg>Changes`],
508 hides: [`href="#i-eye"/></svg>Look`, `>Look<`],
518 hides: [`href="#i-eye"/></svg>Look`, `>Look<`, 'class="where"'],
519 // #281: `?mode=dev` on the same address is unaffected — the where
520 // strip renders exactly as today, dev's own dots and words.
521 dev: ["workbench", "this machine"],
509522 },
510523 {
511524 word: "tell",
@@ -539,7 +552,8 @@ describe("dev's vocabulary never reaches a studio page", () => {
539552 // #171: the composer's close button used to say "Close pull
540553 // request" here, dev's own noun on the studio's own page.
541554 shows: ["Close change"],
542 dev: ["diff-table", openBranch, "Close pull request"],
555 hides: ['class="where"'],
556 dev: ["diff-table", openBranch, "Close pull request", "workbench"],
543557 devTitle: `${openPullTitle} · vocab`,
544558 },
545559 {
test/web-origins.test.ts +29
@@ -16,6 +16,7 @@ import { tmpdir } from "node:os";
1616 import { join } from "node:path";
1717 import { createApp, type App } from "../src/app.ts";
1818 import { serve, type Servers } from "../src/http/server.ts";
19 import { enableApp } from "../src/actions/apps.ts";
1920 import { resetConnectLimit } from "../src/actions/origins.ts";
2021 import { createToken } from "../src/actions/users.ts";
2122 import { listOrigins } from "../src/core/origins.ts";
@@ -735,6 +736,34 @@ describe("origins on the web", () => {
735736 expect(write.status).toBe(403);
736737 });
737738
739 // ---- studio mode on a mirrored app (#281) ----
740
741 test("in studio mode, a mirrored app says where it reads from in one line, never dev's where strip", async () => {
742 const aliceUser = getUserByName(app.db, "alice")!;
743 const repo = getRepo(app.db, "alice", "widgets")!;
744 const enabled = enableApp(app, aliceUser, repo, { slug: "widgets" });
745 expect(enabled.ok).toBe(true);
746
747 const studioHtml = await (
748 await asUser(`${alice}; forge_mode=studio`, "/alice/widgets")
749 ).text();
750 expect(studioHtml).not.toContain('class="where"');
751 expect(studioHtml).not.toContain("workbench");
752 expect(studioHtml).not.toContain("this machine");
753 // Still says where it is read from, and when it last synced — in one
754 // line, the studio's own words rather than dev's three-place strip.
755 expect(studioHtml).toContain(`${originServers.url}/octo/widgets`);
756 expect(studioHtml).toContain("synced");
757
758 // `?mode=dev` on the same address is unaffected: same dots, same words.
759 const devHtml = await (
760 await asUser(`${alice}; forge_mode=dev`, "/alice/widgets")
761 ).text();
762 expect(devHtml).toContain('class="where"');
763 expect(devHtml).toContain("workbench");
764 expect(devHtml).toContain("this machine");
765 });
766
738767 test("disconnecting is confirmed on a page that says what it costs", async () => {
739768 const id = listOrigins(app.db, getUserByName(app.db, "alice")!.id)[0]!.id;
740769 const confirm = await asUser(

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs