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

The terminal shows a forge's claim link as a QR beside the link, so a phone takes it from the screen #11759

Merged opened this

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

Merged

torque/forge:torque/11460-the-terminal-shows-a-forge-s-claim-link → main 3 commits fork 2 threads · 2 unresolved

  • Merged. torque/11460-the-terminal-shows-a-forge-s-claim-link was merged into main as 259e16c.

Review

Sign in to review this change.
8 files changed +144 −13
scripts/unreached-allow.json +1 −2
@@ -1,6 +1,5 @@
11 [
22 "src/perf/page_budget.ts",
33 "src/perf/page_budget_read.ts",
4 "src/spawn/pod.ts",
5 "src/tui/ui/qr.ts"
4 "src/spawn/pod.ts"
65 ]
src/tui/screens/common.ts +36 −7
@@ -25,11 +25,13 @@ import { G, Screen as Surface, type Rect } from "../term/screen.ts";
2525 import { style } from "../term/style.ts";
2626 import { pad, truncate, wrap } from "../term/width.ts";
2727 import { fill, len, split } from "../ui/layout.ts";
28 import { drawQr, qrSize, terminalQr } from "../ui/qr.ts";
2829 import type { Tokens } from "../ui/theme.ts";
2930 import { icon, type IconSet } from "../ui/icons.ts";
3031 import {
3132 card,
3233 dialog,
34 DIALOG_CHROME_ROWS,
3335 input,
3436 row,
3537 scrollbar,
@@ -851,8 +853,17 @@ export function openOnWeb(
851853 * terminal's own drag-to-select work inside a mouse-aware app. `c` copies
852854 * through OSC 52 as well; `enter` opens a browser when one is reachable.
853855 */
854 export function linkScreen(ctx: Ctx, url: string, parent?: Screen): Screen {
856 export function linkScreen(
857 ctx: Ctx,
858 url: string,
859 parent?: Screen,
860 opts: { qr?: boolean } = {},
861 ): Screen {
855862 ctx.mouse(false);
863 // Encoded once, here, from exactly the link shown; held in this closure
864 // only — never logged, never written anywhere. Render only draws it, so a
865 // resize redraws the same cells and prints nothing twice.
866 const code = opts.qr === true ? terminalQr(url) : undefined;
856867 return {
857868 title: "Link",
858869 keys: [
@@ -867,16 +878,34 @@ export function linkScreen(ctx: Ctx, url: string, parent?: Screen): Screen {
867878 },
868879 render(area, s) {
869880 parent?.render(area, s);
881 const text = [
882 url,
883 "",
884 "Select the address to copy it: the mouse is yours here.",
885 "c copies through the terminal too, when it allows it.",
886 ];
887 // The QR goes between the link and the help, only when the whole code
888 // fits inside the dialog; otherwise the link alone, as before.
889 const size = code === undefined ? undefined : qrSize(code);
890 const width = Math.max(54, size === undefined ? 0 : size.cols + 4, [...url].length + 4);
891 const room =
892 size !== undefined &&
893 width <= area.w - 4 &&
894 text.length + 1 + size.rows + DIALOG_CHROME_ROWS <= area.h - 2;
895 // Two lines (the link, a blank) come first; the QR takes the blank
896 // rows reserved for it, its origin given by the dialog's own body rect.
897 const body = room
898 ? [url, "", ...new Array<string>(size.rows).fill(""), "", ...text.slice(2)]
899 : text;
870900 dialog(s, area, ctx.theme.c, {
871901 title: "Link",
872 body: [
873 url,
874 "",
875 "Select the address to copy it: the mouse is yours here.",
876 "c copies through the terminal too, when it allows it.",
877 ],
902 body,
878903 actions: [{ label: "Copy" }, { label: "Close" }],
879904 selected: 0,
905 width,
outdated

The +6 / +2 / +1 offsets are tied to dialog's internal padding by hand. If dialog changes, the QR draws over the help text. Take the body origin from the dialog (return its rect) or pin it with a test on the exact row of the first QR line.

906 ...(room && code !== undefined
907 ? { paint: (b: Rect) => void drawQr(s, { x: b.x, y: b.y + 2, w: b.w, h: size.rows }, code) }
908 : {}),
880909 });
881910 },
882911 key(e) {
src/tui/screens/forge_it.ts +1 −1
@@ -42,7 +42,7 @@ export function forgeItScreen(f: Session, ctx: Ctx, repo: Repo, parent: Screen):
4242 if (typeof link !== "string")
4343 return ctx.toast(`${String(made.body["name"])} was forged before; its link is not shown again`, "attn");
4444 ctx.toast(`${String(made.body["name"])} is ready`, "success");
45 ctx.replace(linkScreen(ctx, link));
45 ctx.replace(linkScreen(ctx, link, undefined, { qr: true }));
4646 });
4747 },
4848 });
src/tui/screens/newforge.ts +1 −1
@@ -156,7 +156,7 @@ export function newForgeScreen(f: Session, ctx: Ctx, initial: Record<string, str
156156 const name = String(body["name"] ?? val("name"));
157157 if (typeof link === "string") {
158158 ctx.toast(`${name} is ready`, "success");
159 ctx.replace(linkScreen(ctx, link));
159 ctx.replace(linkScreen(ctx, link, undefined, { qr: true }));
160160 } else {
161161 ctx.toast(`${name} already exists; its link is not shown again`, "attn");
162162 ctx.pop();
src/tui/screens/startforge.ts +1 −1
@@ -43,7 +43,7 @@ export function startForgeScreen(f: Session, ctx: Ctx): Screen {
4343 return;
4444 }
4545 ctx.toast(`${String(made.body["name"] ?? name)} is ready`, "success");
46 ctx.replace(linkScreen(ctx, link));
46 ctx.replace(linkScreen(ctx, link, undefined, { qr: true }));
4747 } finally {
4848 busy = false;
4949 ctx.invalidate();
src/tui/ui/widgets.ts +7 −1
@@ -714,6 +714,9 @@ export function menu(
714714 * draws the parent first, then this paints over it, and the dialog screen's
715715 * `key()` returns true for everything.
716716 */
717 /** Rows a dialog spends outside its body: border, padding and the button bar. */
718 export const DIALOG_CHROME_ROWS = 6;
719
717720 export function dialog(
718721 s: Screen,
719722 area: Rect,
@@ -724,6 +727,8 @@ export function dialog(
724727 actions: readonly { label: string; variant?: ButtonVariant }[];
725728 selected?: number;
726729 width?: number;
730 /** Called with the body's own rect, to draw something among its lines. */
731 paint?: (body: Rect) => void;
727732 },
728733 ): Rect[] {
729734 // Dim rather than blank: the user keeps their place in the page behind.
@@ -736,7 +741,7 @@ export function dialog(
736741 );
737742 }
738743 const w = Math.min(o.width ?? 54, area.w - 4);
739 const h = Math.min(o.body.length + 6, area.h - 2);
744 const h = Math.min(o.body.length + DIALOG_CHROME_ROWS, area.h - 2);
740745 const r = center(area, w, h);
741746 const inner = card(s, r, c, { title: o.title, focused: true });
742747 o.body.forEach((line, i) => {
@@ -749,6 +754,7 @@ export function dialog(
749754 inner.x + inner.w,
750755 );
751756 });
757 o.paint?.(inner);
752758 const barY = inner.y + inner.h - 1;
753759 const widths = o.actions.map((a) => len(displayWidth(a.label) + 4));
754760 const slots = split({ x: inner.x, y: barY, w: inner.w, h: 1 }, "h", [

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs