Commit Diff


commit - c227bd7f6c0f7d41565ed3a1fd4a788ff9476e76
commit + 00df5eedf8e92b78e7db6d28f85cce5988525b62
blob - f61986f71509bbd3138c9bcdb0c69c885e25cd03
blob + 63d38c4b7f71a7c9c93672e2e0b57dd4c8497a52
--- handoff/verification.md
+++ handoff/verification.md
@@ -218,7 +218,8 @@ states the property, not just examples:
   and matching input-cursor width, narrow-pane fallback, drawer clipping, and
   resize reflow. The curses pty path exercises the styled input and transcript
   through wide curses while retaining clean terminal-mode startup and teardown.
-- **Empty-transcript welcome:** a pure suite pins the exact copy, centering in
+- **Empty-transcript welcome:** a pure suite pins the exact copy, centered
+  copy rows and shared help-column alignment in
   full and drawer-narrowed viewports, ASCII punctuation, whole-row omission on
   short/narrow terminals, nonoverlapping status captions, and one-way
   dismissal. Pure geometry checks keep tiny picker overlays above the status
blob - 4aa6cbfc990bfd2484d1c0005273f56e13d04b82
blob + 6ce6fbd248b4ec24efb550d290158c225e01152a
--- regress/turn/run.sh
+++ regress/turn/run.sh
@@ -1959,7 +1959,7 @@ if [ -x "$obj/ptydrive" ]; then
 	ok "curses: the typed prompt reached the provider" $?
 	grep -a -Fq "OWNER-BLOCK-MARKER" "$dir/cursesout"
 	ok "curses: Owner marker renders over a monochrome pty" $?
-	grep -a -Fq "I like Fugu" "$dir/welcome-stage.0" &&
+	grep -a -Fq "Fugu - Japanese for pufferfish" "$dir/welcome-stage.0" &&
 	    grep -a -Fq "by Isaac Meerleo" "$dir/welcome-stage.0" &&
 	    grep -a -Fq "git ssh://anonymous@got.fugu.farm" \
 	    "$dir/welcome-stage.0"
@@ -1969,9 +1969,9 @@ if [ -x "$obj/ptydrive" ]; then
 	    grep -a -Fq "context used / limit" "$dir/welcome-stage.0"
 	ok "curses: welcome captions label the live status fields" $?
 	grep -a -Fq "conversations" "$dir/welcome-stage.1" &&
-	    grep -a -Fq "I like Fugu" "$dir/welcome-stage.1"
+	    grep -a -Fq "Fugu - Japanese for pufferfish" "$dir/welcome-stage.1"
 	ok "curses: drawer overlays the undismissed welcome" $?
-	grep -a -Fq "I like Fugu" "$dir/welcome-stage.2" &&
+	grep -a -Fq "Fugu - Japanese for pufferfish" "$dir/welcome-stage.2" &&
 	    grep -a -Fq "agent state" "$dir/welcome-stage.2"
 	ok "curses: closing the drawer restores the welcome" $?
 	picker_stage=''
@@ -1982,7 +1982,7 @@ if [ -x "$obj/ptydrive" ]; then
 	done
 	test -n "$picker_stage"
 	ok "curses: model picker overlays the undismissed welcome" $?
-	grep -a -Fq "I like Fugu" "$dir/welcome-stage.5" &&
+	grep -a -Fq "Fugu - Japanese for pufferfish" "$dir/welcome-stage.5" &&
 	    grep -a -Fq "context used / limit" "$dir/welcome-stage.5" &&
 	    ! grep -a -Fq "model: stub-model-a" "$dir/welcome-stage.5"
 	ok "curses: selecting from m restores welcome without scrollback" $?
@@ -1994,12 +1994,12 @@ if [ -x "$obj/ptydrive" ]; then
 	done
 	test -n "$picker_stage"
 	ok "curses: model picker can reopen before Owner input" $?
-	grep -a -Fq "I like Fugu" "$dir/welcome-stage.8" &&
+	grep -a -Fq "Fugu - Japanese for pufferfish" "$dir/welcome-stage.8" &&
 	    grep -a -Fq "context used / limit" "$dir/welcome-stage.8"
 	ok "curses: cancelling the model picker restores the welcome" $?
 	test -s "$dir/welcome-stage.9" &&
 	    grep -a -Fq "OWNER-BLOCK-MARKER" "$dir/welcome-stage.9" &&
-	    ! grep -a -Fq "I like Fugu" "$dir/welcome-stage.9" &&
+	    ! grep -a -Fq "Fugu - Japanese for pufferfish" "$dir/welcome-stage.9" &&
 	    ! grep -a -Fq "agent state" "$dir/welcome-stage.9" &&
 	    ! grep -a -Fq "context used / limit" "$dir/welcome-stage.9"
 	ok "curses: forced repaint after first edit omits welcome and captions" $?
@@ -2007,10 +2007,10 @@ if [ -x "$obj/ptydrive" ]; then
 	ok "curses: pre-input picker selection drives the request" $?
 	grep -a -Fq "Context 46k/111k 41%" "$dir/cursesout"
 	ok "curses: status shows Lead cache plus session context" $?
-	grep -q "I like Fugu" "$dir/reqout" 2>/dev/null
+	grep -q "Fugu - Japanese for pufferfish" "$dir/reqout" 2>/dev/null
 	ok "curses: welcome is absent from the Provider request" \
 	    $([ $? -ne 0 ]; echo $?)
-	grep -q "I like Fugu" "$sessions"/*.ndjson 2>/dev/null
+	grep -q "Fugu - Japanese for pufferfish" "$sessions"/*.ndjson 2>/dev/null
 	ok "curses: welcome is absent from the Journal" \
 	    $([ $? -ne 0 ]; echo $?)
 	n=$(grep -c '"outcome":"ok"' "$sessions"/*.ndjson 2>/dev/null || echo 0)
@@ -2060,7 +2060,7 @@ EOF
 		    "$dir/profileout"
 		ok "curses: $profile subscription warning is not transcript" \
 		    $([ $? -ne 0 ]; echo $?)
-		grep -a -Fq "I like Fugu" "$dir/profileout"
+		grep -a -Fq "Fugu - Japanese for pufferfish" "$dir/profileout"
 		ok "curses: $profile subscription profile retains welcome" $?
 	done
 else
blob - faff7f7a2086a2eaa36bae5ff1a68a662739a4db
blob + 4eacb92d89e483fcaa0099d7acf235e78fd4b52a
--- regress/welcome/welcome_test.c
+++ regress/welcome/welcome_test.c
@@ -30,6 +30,14 @@ find_row(const struct welcome_view *view, const char *
 	return (NULL);
 }
 
+static int
+text_col(const struct welcome_row *row, const char *text)
+{
+	const char *at = strstr(row->text, text);
+
+	return (at == NULL ? -1 : row->col + (int)(at - row->text));
+}
+
 int
 main(void)
 {
@@ -54,7 +62,7 @@ main(void)
 	welcome_build(&view, 0, 100, 30, 0);
 	CHECK(view.nrows == 8);
 	CHECK(strcmp(view.rows[0].text,
-	    "I like Fugu \xe2\x80\x94 Japanese for pufferfish.") == 0);
+	    "Fugu - Japanese for pufferfish") == 0);
 	CHECK(view.rows[0].kind == WELCOME_ROW_TITLE);
 	CHECK(strcmp(view.rows[1].text, "by Isaac Meerleo") == 0);
 	CHECK(view.rows[1].kind == WELCOME_ROW_DIM);
@@ -70,15 +78,18 @@ main(void)
 	    "press Esc, then v             to compose in your editor") == 0);
 	CHECK(strcmp(view.rows[7].text,
 	    "type  /quit<Enter>            to exit") == 0);
+	for (i = 5; i <= 7; i++)
+		CHECK(text_col(&view.rows[i], "to ") ==
+		    text_col(&view.rows[4], "to "));
 	CHECK(view.rows[4].row == view.rows[3].row + 2);
 	for (i = 0; i < view.nrows; i++)
 		CHECK(view.rows[i].col >= 0 &&
 		    view.rows[i].col + view.rows[i].width <= 100);
 
-	/* ASCII mode folds only the em dash, consistently with markdown. */
+	/* The title is already ASCII and remains byte-identical in ASCII mode. */
 	welcome_build(&view, 0, 100, 30, 1);
 	CHECK(strcmp(view.rows[0].text,
-	    "I like Fugu -- Japanese for pufferfish.") == 0);
+	    "Fugu - Japanese for pufferfish") == 0);
 
 	/* Drawer offset and resize recenter without changing the copy. */
 	welcome_build(&view, 31, 69, 20, 0);
@@ -86,6 +97,9 @@ main(void)
 	for (i = 0; i < view.nrows; i++)
 		CHECK(view.rows[i].col >= 31 &&
 		    view.rows[i].col + view.rows[i].width <= 100);
+	for (i = 5; i <= 7; i++)
+		CHECK(text_col(&view.rows[i], "to ") ==
+		    text_col(&view.rows[4], "to "));
 	row = find_row(&view, "by Isaac Meerleo");
 	CHECK(row != NULL && row->col == 31 + (69 - row->width) / 2);
 
@@ -93,7 +107,7 @@ main(void)
 	welcome_build(&view, 0, 100, 4, 0);
 	CHECK(view.nrows == 4);
 	CHECK(strcmp(view.rows[0].text,
-	    "I like Fugu \xe2\x80\x94 Japanese for pufferfish.") == 0);
+	    "Fugu - Japanese for pufferfish") == 0);
 	CHECK(strcmp(view.rows[1].text, "by Isaac Meerleo") == 0);
 	CHECK(strcmp(view.rows[2].text,
 	    "git ssh://anonymous@got.fugu.farm") == 0);
blob - 08bf7571eb0a391928f20d47fc251059e0f3d4f0
blob + bd8d8b8a52b9c15aa981574aa6a339e26038115e
--- src/fugu-tty/welcome.c
+++ src/fugu-tty/welcome.c
@@ -31,8 +31,7 @@ struct welcome_candidate {
 };
 
 static const struct welcome_candidate candidates[WELCOME_ROWS_MAX] = {
-	{ "I like Fugu \xe2\x80\x94 Japanese for pufferfish.",
-	  "I like Fugu -- Japanese for pufferfish.", WELCOME_ROW_TITLE, 0, 0 },
+	{ "Fugu - Japanese for pufferfish", NULL, WELCOME_ROW_TITLE, 0, 0 },
 	{ "by Isaac Meerleo", NULL, WELCOME_ROW_DIM, 0, 1 },
 	{ "git ssh://anonymous@got.fugu.farm", NULL, WELCOME_ROW_DIM, 0, 2 },
 	{ "Fugu is open source and freely available!", NULL,
@@ -99,7 +98,8 @@ welcome_build(struct welcome_view *view, int xoff, int
 {
 	const char	*text[WELCOME_ROWS_MAX];
 	int		 cells[WELCOME_ROWS_MAX], keep[WELCOME_ROWS_MAX];
-	int		 count, gap, groups, i, last_group, row, total, victim;
+	int		 count, gap, groups, help_width = 0, i, last_group;
+	int		 row, total, victim;
 
 	memset(view, 0, sizeof(*view));
 	if (xoff < 0 || width <= 0 || height <= 0)
@@ -129,6 +129,12 @@ welcome_build(struct welcome_view *view, int xoff, int
 	}
 	if (count == 0)
 		return;
+	/* The padded help text is one two-column block.  Center its widest
+	 * retained row once so every row's second column remains aligned. */
+	for (i = 0; i < WELCOME_ROWS_MAX; i++)
+		if (keep[i] && candidates[i].group == 1 &&
+		    cells[i] > help_width)
+			help_width = cells[i];
 
 	row = (height - total) / 2;
 	last_group = -1;
@@ -145,7 +151,8 @@ welcome_build(struct welcome_view *view, int xoff, int
 		out->kind = candidates[i].kind;
 		out->row = row++;
 		out->width = cells[i];
-		out->col = xoff + (width - cells[i]) / 2;
+		out->col = xoff + (width - (candidates[i].group == 1 ?
+		    help_width : cells[i])) / 2;
 		last_group = candidates[i].group;
 	}
 }