Public: anyone can read this; the crew can change it.
small: one name per thing in the account menu: SSH keys, access keys and linked sites read the same on the label, the page title, the empty line and the palette #10818
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/10792-small-one-name-per-thing-in-the-account → main 4 commits fork 5 threads · 5 unresolved
16 files
changed +171
−49
src/actions/origin_words.ts +1 −1
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | /** |
| 2 | 2 | * What an origin's sync and disconnect say, in one place so the page in |
| 3 | * Settings › Origins and the terminal's Connected origins say the same thing. | |
| 3 | * Settings › Origins and the terminal's Origins screen say the same thing. | |
| 4 | 4 | */ |
| 5 | 5 | |
| 6 | 6 | export const NO_SUCH_ORIGIN = "No such origin."; |
src/tui/screens/connections.ts +4 −4
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | /** |
| 2 | * Connected origins in the terminal: the hosts your mirrors come from, as | |
| 2 | * Origins in the terminal: the hosts your mirrors come from, as | |
| 3 | 3 | * Settings › Origins lists them. `s` syncs the selected one now, `d` disconnects |
| 4 | 4 | * it after a confirm — through `actions/origins.ts`, in `origin_words.ts`'s words. |
| 5 | 5 | */ |
| @@ -27,7 +27,7 @@ export function connectionsScreen(f: Session, ctx: Ctx): Screen { | ||
| 27 | 27 | const counts = () => (f.user === undefined ? new Map<string, { count: number }>() : mirrorsByOrigin(f.app.db, f.user.id)); |
| 28 | 28 | |
| 29 | 29 | const screen: Screen = { |
| 30 | title: "Connected origins", | |
| 30 | title: "Origins", | |
| 31 | 31 | keys: [binding(["j", "k"], "move cursor"), binding(["c"], "connect"), binding(["s"], "sync now"), binding(["v"], "verify again"), binding(["d"], "disconnect"), binding(["esc"], "back")], |
| 32 | 32 | render(area, s) { |
| 33 | 33 | const c = ctx.theme.c; |
| @@ -159,10 +159,10 @@ export function connectionsScreen(f: Session, ctx: Ctx): Screen { | ||
| 159 | 159 | return screen; |
| 160 | 160 | } |
| 161 | 161 | |
| 162 | /** Connected origins: a signed-in person, not an agent. */ | |
| 162 | /** Origins: a signed-in person, not an agent. */ | |
| 163 | 163 | export const entry: PaletteEntry = { |
| 164 | 164 | id: "origins", |
| 165 | title: "Connected origins", | |
| 165 | title: "Origins", | |
| 166 | 166 | hint: "the hosts your mirrors come from", |
| 167 | 167 | offered: (session) => session.user !== undefined && session.user.kind !== "agent", |
| 168 | 168 | run: (session, ctx) => ctx.push(connectionsScreen(session, ctx)), |
src/tui/screens/signins.ts +6 −6
| @@ -104,7 +104,7 @@ function hardWrap(text: string, width: number): string[] { | ||
| 104 | 104 | function secretScreen(ctx: Ctx, secret: string, market: { add: string; install: string } | null): Screen { |
| 105 | 105 | let copied = ""; |
| 106 | 106 | return { |
| 107 | title: "New token", | |
| 107 | title: "New access token", | |
| 108 | 108 | keys: [ |
| 109 | 109 | binding(["y"], "copy it"), |
| 110 | 110 | ...(market === null ? [] : [binding(["a"], "copy the marketplace line"), binding(["i"], "copy the install line")]), |
| @@ -222,7 +222,7 @@ export function signInsScreen(f: Session, ctx: Ctx): Screen { | ||
| 222 | 222 | const confirmAdmin = (name: string) => |
| 223 | 223 | later(ctx, () => |
| 224 | 224 | confirmScreen(ctx, screen, { |
| 225 | title: "Make an admin token", | |
| 225 | title: "Make an admin access token", | |
| 226 | 226 | body: [`${name} will act as admin: y to make it, n to cancel.`], |
| 227 | 227 | actions: [{ label: "Make it", variant: "danger" }, { label: "Cancel", variant: "ghost" }], |
| 228 | 228 | yesNo: true, |
| @@ -237,7 +237,7 @@ export function signInsScreen(f: Session, ctx: Ctx): Screen { | ||
| 237 | 237 | const askScope = (name: string, isAdmin: boolean) => |
| 238 | 238 | later(ctx, () => |
| 239 | 239 | promptScreen(ctx, screen, { |
| 240 | title: "Make a token", | |
| 240 | title: "Make an access token", | |
| 241 | 241 | label: isAdmin ? "Scope: ro, rw or admin" : "Scope: ro or rw", |
| 242 | 242 | value: "ro", |
| 243 | 243 | onSubmit: (word) => { |
| @@ -252,7 +252,7 @@ export function signInsScreen(f: Session, ctx: Ctx): Screen { | ||
| 252 | 252 | |
| 253 | 253 | const askName = (isAdmin: boolean) => |
| 254 | 254 | promptScreen(ctx, screen, { |
| 255 | title: "Make a token", | |
| 255 | title: "Make an access token", | |
| 256 | 256 | label: "Name", |
| 257 | 257 | value: "token", |
| 258 | 258 | onSubmit: (name) => { |
| @@ -323,7 +323,7 @@ export function signInsScreen(f: Session, ctx: Ctx): Screen { | ||
| 323 | 323 | keys: [ |
| 324 | 324 | binding(["j", "k"], "move cursor"), |
| 325 | 325 | binding(["d"], "sign a browser out or revoke a token"), |
| 326 | binding(["m"], "make a token"), | |
| 326 | binding(["m"], "make an access token"), | |
| 327 | 327 | binding(["p"], "change your password"), |
| 328 | 328 | ], |
| 329 | 329 | render(area, s) { |
| @@ -337,7 +337,7 @@ export function signInsScreen(f: Session, ctx: Ctx): Screen { | ||
| 337 | 337 | ? "Sign in to see where you are signed in." |
| 338 | 338 | : f.user.kind === "agent" |
| 339 | 339 | ? "Where you are signed in. d signs a browser out or revokes a token." |
| 340 | : "Where you are signed in. d signs a browser out or revokes a token, m makes a token, p changes your password."; | |
| 340 | : "Where you are signed in. d signs a browser out or revokes a token, m makes an access token, p changes your password."; | |
| 341 | 341 | s.text(head!.x, head!.y, truncate(text, head!.w), style(c.mutedFg, c.bg), head!.x + head!.w); |
| 342 | 342 | drawList(s, body!, c, state, rows(all)); |
| 343 | 343 | }, |
src/web/pages/account.ts +10 −10
| @@ -751,7 +751,7 @@ function tokensView(ctx: Ctx): Html { | ||
| 751 | 751 | listSessions(ctx.app.db, ctx.auth.user!.id, cookieValue(ctx.request, "forge_session")), |
| 752 | 752 | ); |
| 753 | 753 | const list = tokens.length === 0 |
| 754 | ? html`<p class="muted">No tokens yet. Mint one when a script, a CI job, or another machine needs to act as you.</p>` | |
| 754 | ? html`<p class="muted">No access tokens yet. Mint one when a script, a CI job, or another machine needs to act as you.</p>` | |
| 755 | 755 | : html`<ul class="list setting-list"> |
| 756 | 756 | ${tokens.map((t) => |
| 757 | 757 | row({ |
| @@ -766,18 +766,18 @@ function tokensView(ctx: Ctx): Html { | ||
| 766 | 766 | </ul>`; |
| 767 | 767 | return html`${help([ |
| 768 | 768 | [ |
| 769 | "Tokens", | |
| 769 | "Access tokens", | |
| 770 | 770 | "Use a token as the password when pushing over HTTPS, or as a Bearer token for the API. Your account password works for git too — tokens are for scoped or automated access.", |
| 771 | 771 | ], |
Principle 8 (plain words, one name): help term and the field label below still say "Token name"; use "Access token name" so the page reads one name. |
||
| 772 | ["Token name", "Name it after where it lives — laptop, CI, deploy box."], | |
| 772 | ["Access token name", "Name it after where it lives — laptop, CI, deploy box."], | |
| 773 | 773 | ["Scope", "A token can never mint another token, whatever its scope."], |
| 774 | 774 | ])}${connectCard(ctx, readFlash(ctx.request)?.code)} |
| 775 | ${block("Tokens", list)} | |
| 775 | ${block("Access tokens", list)} | |
| 776 | 776 | ${block( |
| 777 | "New token", | |
| 777 | "New access token", | |
| 778 | 778 | html`<form method="post" action="/settings/tokens"> |
| 779 | 779 | ${uiField({ |
| 780 | label: "Token name", | |
| 780 | label: "Access token name", | |
| 781 | 781 | name: "name", |
| 782 | 782 | required: true, |
| 783 | 783 | })} |
| @@ -804,7 +804,7 @@ function tokensView(ctx: Ctx): Html { | ||
| 804 | 804 | </div>` |
| 805 | 805 | : null} |
| 806 | 806 | <div class="form-actions"> |
| 807 | ${btn("Generate token", { variant: "primary" })} | |
| 807 | ${btn("Generate access token", { variant: "primary" })} | |
| 808 | 808 | </div> |
| 809 | 809 | </form>`, |
| 810 | 810 | )} |
| @@ -816,7 +816,7 @@ export function tokensPage(ctx: Ctx): Response { | ||
| 816 | 816 | if (!ctx.auth.user) return signInFirst(ctx); |
| 817 | 817 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); |
| 818 | 818 | const body = tokensView(ctx); |
| 819 | return timePhase(ctx.request, "render", () => accountShell(ctx, "tokens", "Personal access tokens", body)); | |
| 819 | return timePhase(ctx.request, "render", () => accountShell(ctx, "tokens", "Access tokens", body)); | |
| 820 | 820 | } |
| 821 | 821 | |
| 822 | 822 | // POST /settings/tokens |
| @@ -909,7 +909,7 @@ function keysView( | ||
| 909 | 909 | ${block( |
| 910 | 910 | "SSH keys", |
| 911 | 911 | keys.length === 0 |
| 912 | ? html`<p class="muted">No keys yet. Add one and git over SSH stops asking for a password.</p>` | |
| 912 | ? html`<p class="muted">No SSH keys yet. Add one and git over SSH stops asking for a password.</p>` | |
| 913 | 913 | : html`<ul class="list setting-list"> |
| 914 | 914 | ${keys.map((k) => |
| 915 | 915 | row({ |
| @@ -940,7 +940,7 @@ function keysView( | ||
| 940 | 940 | placeholder: "ssh-ed25519 AAAA…", |
| 941 | 941 | })} |
| 942 | 942 | <div class="form-actions"> |
| 943 | ${btn("Add key", { variant: "primary" })} | |
| 943 | ${btn("Add SSH key", { variant: "primary" })} | |
| 944 | 944 | </div> |
| 945 | 945 | ${refusal(values.error)} |
| 946 | 946 | </form>`, |
src/web/pages/origins.ts +3 −3
| @@ -794,14 +794,14 @@ function originsView( | ||
| 794 | 794 | </p> |
| 795 | 795 | ${origins.length === 0 |
| 796 | 796 | ? html`<section class="settings-block"> |
Principle 8: the block heading above is "Connected origins" while the menu, title and h1 say "Origins"; pick one name for the section too. |
||
| 797 | <h2>Connected origins</h2> | |
| 797 | <h2>Origins</h2> | |
Web says 'Origins'; the terminal and src/actions/origin_words.ts still say 'Connected origins'. Align them so the two doors read the same. |
||
| 798 | 798 | <p class="small"> |
| 799 | No origins connected: connect another forge, GitHub, a Forgejo | |
| 799 | No origins yet: connect another forge, GitHub, a Forgejo | |
| 800 | 800 | instance or a Gitea instance below. |
| 801 | 801 | </p> |
| 802 | 802 | </section>` |
| 803 | 803 | : html`<section class="settings-block"> |
| 804 | <h2>Connected origins</h2> | |
| 804 | <h2>Origins</h2> | |
| 805 | 805 | <ul class="list setting-list" data-origins> |
| 806 | 806 | ${origins.map((o) => |
| 807 | 807 | originCard( |
test/account-rows.test.ts +4 −4
| @@ -63,8 +63,8 @@ test("no account page is a card or holds a table", async () => { | ||
| 63 | 63 | test("tokens are rows under a Tokens heading, each with Revoke", async () => { |
| 64 | 64 | const m = await main("/settings/tokens"); |
| 65 | 65 | expect(m).toContain('<section class="settings-block">'); |
| 66 | const list = m.slice(m.indexOf("<h2>Tokens</h2>")); | |
| 67 | expect(list).toMatch(/^<h2>Tokens<\/h2>\s*<ul class="list setting-list">/); | |
| 66 | const list = m.slice(m.indexOf("<h2>Access tokens</h2>")); | |
| 67 | expect(list).toMatch(/^<h2>Access tokens<\/h2>\s*<ul class="list setting-list">/); | |
| 68 | 68 | const item = list.slice(0, list.indexOf("</ul>")); |
| 69 | 69 | expect(item).toContain("<strong>laptop</strong>"); |
| 70 | 70 | expect(item).toMatch(/settings%2Ftokens%2F[^"]+%2Fdelete/); |
| @@ -90,10 +90,10 @@ test("empty lists are one muted sentence, not a panel", async () => { | ||
| 90 | 90 | const c = (other.headers.get("set-cookie") ?? "").split(";")[0]!; |
| 91 | 91 | const get = async (path: string) => (await (await fetch(`${base}${path}`, { headers: { cookie: c } })).text()); |
| 92 | 92 | const tokens = await get("/settings/tokens"); |
| 93 | expect(tokens).toContain('<p class="muted">No tokens yet.'); | |
| 93 | expect(tokens).toContain('<p class="muted">No access tokens yet.'); | |
| 94 | 94 | expect(tokens).not.toContain("empty-title"); |
| 95 | 95 | const keys = await get("/settings/keys"); |
| 96 | expect(keys).toContain('<p class="muted">No keys yet.'); | |
| 96 | expect(keys).toContain('<p class="muted">No SSH keys yet.'); | |
| 97 | 97 | expect(keys).not.toContain("empty-title"); |
| 98 | 98 | const card = String(sessionsCard([], "/settings")); |
| 99 | 99 | expect(card).toContain('<p class="muted">Not signed in on any browser.</p>'); |
test/keys.test.ts +1 −1
| @@ -284,7 +284,7 @@ describe.skipIf(NO_KEYGEN)("ssh keys", () => { | ||
| 284 | 284 | const empty = await ( |
| 285 | 285 | await fetch(`${base}/settings/keys`, { headers: { cookie } }) |
| 286 | 286 | ).text(); |
| 287 | expect(empty).toContain("No keys yet"); | |
| 287 | expect(empty).toContain("No SSH keys yet"); | |
| 288 | 288 | |
| 289 | 289 | const added = await fetch(`${base}/settings/keys`, { |
| 290 | 290 | method: "POST", |
test/now-links.test.ts +1 −1
| @@ -119,7 +119,7 @@ const MENU_TITLES: Record<string, string> = { | ||
| 119 | 119 | Migrate: "Migrate", |
| 120 | 120 | "New forge": "New forge", |
| 121 | 121 | Settings: "Settings", |
| 122 | "Access tokens": "Personal access tokens", | |
| 122 | "Access tokens": "Access tokens", | |
| 123 | 123 | "SSH keys": "SSH keys", |
| 124 | 124 | Origins: "Origins", |
| 125 | 125 | Crew: "Crew", |
test/onboarding.test.ts +1 −1
| @@ -123,7 +123,7 @@ describe("onboarding: a person with just a login", () => { | ||
| 123 | 123 | const pageHtml = await ( |
| 124 | 124 | await fetch(`${base}/settings/tokens`, { headers: { cookie } }) |
| 125 | 125 | ).text(); |
| 126 | expect(pageHtml).toContain("Personal access tokens"); | |
| 126 | expect(pageHtml).toContain("Access tokens"); | |
| 127 | 127 | |
| 128 | 128 | // Post-redirect-get: minting redirects, and the secret rides a one-shot |
| 129 | 129 | // flash cookie so a refresh shows the list instead of minting again. |
test/origins-page-shape.test.ts +5 −5
| @@ -50,14 +50,14 @@ test("the origins page has settings blocks with headings and no card", async () | ||
| 50 | 50 | expect(res.status).toBe(200); |
| 51 | 51 | const body = await res.text(); |
| 52 | 52 | expect(body).not.toMatch(/class="(?:[^"]*\s)?card(?:\s[^"]*)?"/); |
| 53 | for (const h of ["Connected origins", "Trusted internal hosts", "Connect an origin"]) { | |
| 53 | for (const h of ["Origins", "Trusted internal hosts", "Connect an origin"]) { | |
| 54 | 54 | expect(body).toMatch(new RegExp(`<section class="settings-block"[^>]*>\\s*<h2>${h}</h2>`)); |
| 55 | 55 | } |
| 56 | 56 | expect(body).toContain( |
| 57 | "No origins connected: connect another forge, GitHub, a Forgejo\n instance or a Gitea instance below.", | |
| 57 | "No origins yet: connect another forge, GitHub, a Forgejo\n instance or a Gitea instance below.", | |
| 58 | 58 | ); |
| 59 | 59 | // One sentence: no full stop before its end. |
| 60 | const empty = body.split("<h2>Connected origins</h2>")[1]!.split("</p>")[0]!; | |
| 60 | const empty = body.split("<h2>Origins</h2>")[1]!.split("</p>")[0]!; | |
| 61 | 61 | expect(empty.replace(/<[^>]+>/g, "").trim().split(". ").length).toBe(1); |
| 62 | 62 | expect(body).toContain('id="connect"'); |
| 63 | 63 | }); |
| @@ -77,7 +77,7 @@ test("connected origins are rows of one list, with no wrapper box", async () => | ||
| 77 | 77 | } |
| 78 | 78 | const body = await (await fetch(`${servers.url}/settings/origins`, { headers: { cookie } })).text(); |
| 79 | 79 | expect(body).not.toContain("origin-item"); |
| 80 | const block = body.split("<h2>Connected origins</h2>")[1]!.split("</section>")[0]!; | |
| 80 | const block = body.split("<h2>Origins</h2>")[1]!.split("</section>")[0]!; | |
| 81 | 81 | expect(block.match(/<ul class="list setting-list" data-origins>/g)?.length).toBe(1); |
| 82 | 82 | expect(block.match(/<li[ >]/g)!.length).toBeGreaterThanOrEqual(2); |
| 83 | 83 | expect(block).toContain("Sync now"); |
| @@ -100,7 +100,7 @@ test("an origin's error and notes render inside its own row", async () => { | ||
| 100 | 100 | .query("UPDATE origin SET last_error = ?1 WHERE id = ?2") |
| 101 | 101 | .run("upstream said 502", o.value.id); |
| 102 | 102 | const body = await (await fetch(`${servers.url}/settings/origins`, { headers: { cookie } })).text(); |
| 103 | const block = body.split("<h2>Connected origins</h2>")[1]!.split("</section>")[0]!; | |
| 103 | const block = body.split("<h2>Origins</h2>")[1]!.split("</section>")[0]!; | |
| 104 | 104 | const li = block |
| 105 | 105 | .split(/<li[ >]/) |
| 106 | 106 | .slice(1) |
test/palette-roster.test.ts +3 −3
| @@ -156,7 +156,7 @@ test("a site-admin person is offered the whole roster", () => { | ||
| 156 | 156 | ["cloud", "Cloud accounts", "provider tokens, what they are used for, today's calls"], |
| 157 | 157 | ["pinned", "Pinned forges", "deploy, update and roll back a forge on your cloud"], |
| 158 | 158 | ["bets", "Bets", "back, stop and finish a repository\'s bets"], |
| 159 | ["origins", "Connected origins", "the hosts your mirrors come from"], | |
| 159 | ["origins", "Origins", "the hosts your mirrors come from"], | |
| 160 | 160 | ["domains", "Failure domains", "failure domains and their last drill"], |
| 161 | 161 | ["feed", "Feed", "what happened, ranked; r replies on a post about an issue or pull, l likes, h who liked"], |
| 162 | 162 | ["feature-review", "Menu entries to review", "absorbed apps whose declared name or page changed: accept them"], |
| @@ -248,7 +248,7 @@ test("a site-admin agent is offered the roster without the person-only entries", | ||
| 248 | 248 | ]); |
| 249 | 249 | }); |
| 250 | 250 | |
| 251 | test("no agent is offered Connected origins", () => { | |
| 251 | test("no agent is offered Origins", () => { | |
| 252 | 252 | expect(roster({ ...admin, kind: "agent" }).map(([id]) => id)).not.toContain("origins"); |
| 253 | 253 | }); |
| 254 | 254 | |
| @@ -313,7 +313,7 @@ test("a member person is offered the signed-in roster without the admin entries" | ||
| 313 | 313 | ["archived", "Archived repositories", "put away, not gone: open one or wake it"], |
| 314 | 314 | ["bulk", "Bulk actions", "your last bulk actions; u undoes one inside its day"], |
| 315 | 315 | ["bets", "Bets", "back, stop and finish a repository\'s bets"], |
| 316 | ["origins", "Connected origins", "the hosts your mirrors come from"], | |
| 316 | ["origins", "Origins", "the hosts your mirrors come from"], | |
| 317 | 317 | ["feed", "Feed", "what happened, ranked; r replies on a post about an issue or pull, l likes, h who liked"], |
| 318 | 318 | ["feature-feed", "Feature feeds", "the feed items about an absorbed app, as Settings › Features shows them"], |
| 319 | 319 | ["front", "Front page", "the apps a first visitor sees, ranked; an admin pins or hides"], |
test/tui-origins-forge.test.ts +2 −2
| @@ -73,7 +73,7 @@ test("c connects another forge by address and token, and the list says what did | ||
| 73 | 73 | }; |
| 74 | 74 | try { |
| 75 | 75 | await key("k"); |
| 76 | await type("Connected origins"); | |
| 76 | await type("Origins"); | |
| 77 | 77 | await key("enter"); |
| 78 | 78 | expect(app.screen.toText()).toContain("Press c to connect one."); |
| 79 | 79 | // A forge has no device sign-in: the terminal says so and points at the page. |
| @@ -123,7 +123,7 @@ test("a token the forge refuses connects nothing and says so", async () => { | ||
| 123 | 123 | }; |
| 124 | 124 | try { |
| 125 | 125 | await press("k", true); |
| 126 | await type("Connected origins"); | |
| 126 | await type("Origins"); | |
| 127 | 127 | await press("enter"); |
| 128 | 128 | await press("c"); |
| 129 | 129 | await type(friendServers.url); |
test/tui-origins-guard.test.ts +1 −1
| @@ -68,7 +68,7 @@ describe("a paused origin in the terminal", () => { | ||
| 68 | 68 | }; |
| 69 | 69 | try { |
| 70 | 70 | await key("k", true); |
| 71 | for (const ch of "Connected origins") await key(ch); | |
| 71 | for (const ch of "Origins") await key(ch); | |
| 72 | 72 | await key("enter"); |
| 73 | 73 | expect(app.screen.toText()).toContain("writes paused"); |
| 74 | 74 | expect(app.screen.toText()).toContain("Press v to check again"); |
test/tui-origins.test.ts +5 −5
| @@ -1,4 +1,4 @@ | ||
| 1 | /** Connected origins in the terminal: listed, disconnected after a confirm, never offered to a guest. */ | |
| 1 | /** Origins in the terminal: listed, disconnected after a confirm, never offered to a guest. */ | |
| 2 | 2 | |
| 3 | 3 | import { afterAll, beforeAll, describe, expect, test } from "bun:test"; |
| 4 | 4 | import { mkdtemp, rm } from "node:fs/promises"; |
| @@ -47,7 +47,7 @@ describe("connected origins in the terminal", () => { | ||
| 47 | 47 | |
| 48 | 48 | const open = async (h: ReturnType<typeof boot>) => { |
| 49 | 49 | await h.ctrl("k"); |
| 50 | await h.type("Connected origins"); | |
| 50 | await h.type("Origins"); | |
| 51 | 51 | return h; |
| 52 | 52 | }; |
| 53 | 53 | |
| @@ -116,7 +116,7 @@ describe("connected origins in the terminal", () => { | ||
| 116 | 116 | const h = boot({ sshUsername: "t", remoteAddress: "127.0.0.1", user: getUserByName(forge.db, "carol")! } as TuiIdentity); |
| 117 | 117 | try { |
| 118 | 118 | await h.ctrl("k"); |
| 119 | await h.type("Connected origins"); | |
| 119 | await h.type("Origins"); | |
| 120 | 120 | await h.press("enter"); |
| 121 | 121 | expect(listOrigins(forge.db, made.value.id)[0]!.lastError).toBe(""); |
| 122 | 122 | await h.press("s"); |
| @@ -167,7 +167,7 @@ describe("connected origins in the terminal", () => { | ||
| 167 | 167 | const h = boot({ ...alice(), user: { ...getUserByName(forge.db, "alice")!, kind: "agent" } } as TuiIdentity); |
| 168 | 168 | try { |
| 169 | 169 | await h.ctrl("k"); |
| 170 | await h.type("Connected origins"); | |
| 170 | await h.type("Origins"); | |
| 171 | 171 | expect(h.text()).not.toContain("the hosts your mirrors come from"); |
| 172 | 172 | } finally { |
| 173 | 173 | h.stop(); |
| @@ -200,7 +200,7 @@ describe("connected origins in the terminal", () => { | ||
| 200 | 200 | const h = boot({ sshUsername: "git", remoteAddress: "127.0.0.1", fingerprint: "SHA256:unknown" }); |
| 201 | 201 | try { |
| 202 | 202 | await h.ctrl("k"); |
| 203 | await h.type("Connected origins"); | |
| 203 | await h.type("Origins"); | |
| 204 | 204 | expect(h.text()).not.toContain("the hosts your mirrors come from"); |
| 205 | 205 | } finally { |
| 206 | 206 | h.stop(); |
test/web-origins.test.ts +2 −2
| @@ -232,7 +232,7 @@ describe("origins on the web", () => { | ||
| 232 | 232 | |
| 233 | 233 | test("the origins page starts empty and offers the three ways in", async () => { |
| 234 | 234 | const html = await (await asUser(alice, "/settings/origins")).text(); |
| 235 | expect(html).toContain("No origins connected"); | |
| 235 | expect(html).toContain("No origins yet"); | |
| 236 | 236 | expect(html).toContain("Forgejo / Gitea, one click"); |
| 237 | 237 | // Another forge leads (#9388); the rest are folded. |
| 238 | 238 | expect(html).toContain("Another forge"); |
| @@ -363,7 +363,7 @@ describe("origins on the web", () => { | ||
| 363 | 363 | |
| 364 | 364 | test("another user cannot see or act on that origin", async () => { |
| 365 | 365 | const html = await (await asUser(bob, "/settings/origins")).text(); |
| 366 | expect(html).toContain("No origins connected"); | |
| 366 | expect(html).toContain("No origins yet"); | |
| 367 | 367 | const id = listOrigins(app.db, getUserByName(app.db, "alice")!.id)[0]!.id; |
| 368 | 368 | const stolen = await asUser(bob, `/settings/origins/${id}/repos`); |
| 369 | 369 | expect(stolen.status).toBe(404); |
Terminal now says 'access token' here, but the terminal's origins screen is still 'Connected origins' (connections.ts:30,165). One name per thing must hold in both doors; rename it and the palette entry.