fix(web): install-prompt Step 2 + restart cue match Desktop install path

/home page Step 2 told users to mkdir ~/Desktop/<workspace_dir>, but the
pasted install script's Step 2 pwd-check expected $HOME/<workspace_dir>
and warned 'normally installed in ~/FoundryAI' — sending users on the
Desktop path through an unnecessary 'install here' confirmation.

Align Step 2 (pwd check + warning copy + manual mkdir hint), Step 9
restart-claude cue, post-install /home hero, and the 'don't create
Projects/' callout to ~/Desktop/<workspace_dir>. Update tests.
This commit is contained in:
Vojtech Rysanek 2026-05-21 18:17:25 +04:00
parent 9f15af85f0
commit 9e3e611aab
6 changed files with 40 additions and 33 deletions

View file

@ -87,6 +87,13 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
standard step-lede size instead of the previous 13px chip. standard step-lede size instead of the previous 13px chip.
### Fixed ### Fixed
- Install-script Step 2 + Step 9 restart cue + post-install `/home` hero
now reference `~/Desktop/<workspace_dir>` to match the `/home` "Step 2
— pick a folder" recommendation users actually run (`mkdir -p
~/Desktop/<workspace_dir>`). Previously the pasted setup script
checked `pwd` against `$HOME/<workspace_dir>` and would warn
"Foundry AI is normally installed in ~/FoundryAI" even though the
/home page had just sent the user to `~/Desktop/FoundryAI`.
- Pre-login pages (`/login`, magic-link screens, first-time `/setup`) - Pre-login pages (`/login`, magic-link screens, first-time `/setup`)
now honour the configured `instance.theme`. `base_login.html` sets now honour the configured `instance.theme`. `base_login.html` sets
`<html data-theme="...">` from `instance_theme`, additionally loads `<html data-theme="...">` from `instance_theme`, additionally loads

View file

@ -337,19 +337,19 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
"""Steps 2-4 — workspace folder check, then `agnes init` + smoke verify. """Steps 2-4 — workspace folder check, then `agnes init` + smoke verify.
Step 2 verifies the user is already cd'd into the workspace folder Step 2 verifies the user is already cd'd into the workspace folder
that the /home onboarding page's visible "Step 2 — create your that the /home onboarding page's visible "Step 2 — pick a folder"
workspace folder" told them to create manually (`mkdir -p told them to create manually (`mkdir -p ~/Desktop/{workspace_dir}
~/{workspace_dir} && cd ~/{workspace_dir}`). The pasted script && cd ~/Desktop/{workspace_dir}`). The pasted script DOES NOT
DOES NOT auto-create the folder that would silently override an auto-create the folder that would silently override an
intentional choice to install at a different path (e.g. the user intentional choice to install at a different path (e.g. the user
cd'd to `~/work/agnes-prod` on purpose). Instead we `pwd`, compare cd'd to `~/work/agnes-prod` on purpose). Instead we `pwd`, compare
to `$HOME/{workspace_dir}`, and on mismatch warn loudly and ask the to `$HOME/Desktop/{workspace_dir}`, and on mismatch warn loudly and
user to either re-paste from the right folder or explicitly confirm ask the user to either re-paste from the right folder or explicitly
"install here" in the current cwd. confirm "install here" in the current cwd.
`{workspace_dir}` and `{instance_brand}` are placeholders pre-substituted `{workspace_dir}` and `{instance_brand}` are placeholders pre-substituted
by :func:`resolve_lines` from the operator-configured brand. Defaults by :func:`resolve_lines` from the operator-configured brand. Defaults
keep `~/Agnes` behavior for instances that don't set the brand knob. keep `~/Desktop/Agnes` behavior for instances that don't set the brand knob.
`agnes init --workspace .` continues to use the current cwd, so once `agnes init --workspace .` continues to use the current cwd, so once
step 2 has confirmed (or the user has explicitly accepted) the step 2 has confirmed (or the user has explicitly accepted) the
@ -373,24 +373,24 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
return [ return [
"", "",
"2) Verify the user is already in the workspace folder.", "2) Verify the user is already in the workspace folder.",
" The /home page's visible \"Step 2 — create your workspace folder\"", " The /home page's visible \"Step 2 — pick a folder\"",
" already asked the user to run", " already asked the user to run",
" mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}", " mkdir -p ~/Desktop/{workspace_dir} && cd ~/Desktop/{workspace_dir}",
" in their terminal BEFORE pasting this script. Do NOT silently", " in their terminal BEFORE pasting this script. Do NOT silently",
" re-create the folder here — the user may have intentionally cd'd", " re-create the folder here — the user may have intentionally cd'd",
" to a different location to install {instance_brand} there instead.", " to a different location to install {instance_brand} there instead.",
"", "",
" Run:", " Run:",
" pwd", " pwd",
" Expected: $HOME/{workspace_dir} (i.e. ~/{workspace_dir})", " Expected: $HOME/Desktop/{workspace_dir} (i.e. ~/Desktop/{workspace_dir})",
"", "",
" If `pwd` matches the expected path: continue silently to step 3.", " If `pwd` matches the expected path: continue silently to step 3.",
"", "",
" If `pwd` does NOT match, STOP and tell the user verbatim:", " If `pwd` does NOT match, STOP and tell the user verbatim:",
"", "",
" \"You are in <current-pwd>, but {instance_brand} is normally", " \"You are in <current-pwd>, but {instance_brand} is normally",
" installed in ~/{workspace_dir} (see /home Step 2). Either run", " installed in ~/Desktop/{workspace_dir} (see /home Step 2). Either run",
" mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}", " mkdir -p ~/Desktop/{workspace_dir} && cd ~/Desktop/{workspace_dir}",
" in your terminal now and re-paste this setup script, OR reply", " in your terminal now and re-paste this setup script, OR reply",
" 'install here' to install {instance_brand} in <current-pwd>", " 'install here' to install {instance_brand} in <current-pwd>",
" instead. Reply 'abort' to stop.\"", " instead. Reply 'abort' to stop.\"",
@ -549,7 +549,7 @@ def _restart_claude_lines(step_num: str) -> list[str]:
return [ return [
"", "",
f"{step_num}) Restart Claude Code so every plugin, MCP server, and SessionStart hook installed above actually loads:", f"{step_num}) Restart Claude Code so every plugin, MCP server, and SessionStart hook installed above actually loads:",
" Tell me to type `/exit` (or close the Claude Code session entirely), then run `claude` again from this same directory — the install dir confirmed in step 2 (`~/{workspace_dir}` on the default path, or whatever cwd the user explicitly accepted with 'install here').", " Tell me to type `/exit` (or close the Claude Code session entirely), then run `claude` again from this same directory — the install dir confirmed in step 2 (`~/Desktop/{workspace_dir}` on the default path, or whatever cwd the user explicitly accepted with 'install here').",
" The next session boots with all marketplace plugins, every connector's keychain entries / OAuth grants, and the agnes-welcome + refresh-marketplace SessionStart hooks active. This is the last action before the Confirm summary — once I'm back in Claude Code, setup is complete.", " The next session boots with all marketplace plugins, every connector's keychain entries / OAuth grants, and the agnes-welcome + refresh-marketplace SessionStart hooks active. This is the last action before the Confirm summary — once I'm back in Claude Code, setup is complete.",
] ]

View file

@ -2485,7 +2485,7 @@ What can I help you with?
{% endwith %} {% endwith %}
</div> </div>
<div class="install-note"> <div class="install-note">
The preview above is the exact text the button copies; the placeholder is replaced with a real token at click time. Don't create <code>~/{{ workspace_dir }}/Projects/</code> manually — the bundled plugin offers to set it up after install. The preview above is the exact text the button copies; the placeholder is replaced with a real token at click time. Don't create <code>~/Desktop/{{ workspace_dir }}/Projects/</code> manually — the bundled plugin offers to set it up after install.
</div> </div>
</details> </details>
</div> </div>
@ -2607,7 +2607,7 @@ What can I help you with?
{% if onboarded %} {% if onboarded %}
{# Offboarding escape hatch shown only after the hero has disappeared. {# Offboarding escape hatch shown only after the hero has disappeared.
Lets the analyst (e.g. after wiping ~/{{ workspace_dir }}) flip the Lets the analyst (e.g. after wiping ~/Desktop/{{ workspace_dir }}) flip the
users.onboarded boolean back to false so the full install hero users.onboarded boolean back to false so the full install hero
renders again on next reload. Discrete by design — onboarded renders again on next reload. Discrete by design — onboarded
users land on /home expecting the nav hub, not a setup screen. #} users land on /home expecting the nav hub, not a setup screen. #}

View file

@ -114,7 +114,7 @@
<div class="eyebrow">Welcome back, {{ display_name }}</div> <div class="eyebrow">Welcome back, {{ display_name }}</div>
<h1>You're all set up</h1> <h1>You're all set up</h1>
<p> <p>
Open Claude Code in any project under <code>~/{{ workspace_dir }}/Projects/</code> Open Claude Code in any project under <code>~/Desktop/{{ workspace_dir }}/Projects/</code>
and start a session — your data and plugins are already synced. Use the cards below to jump into the parts of {{ instance_brand }} you need. and start a session — your data and plugins are already synced. Use the cards below to jump into the parts of {{ instance_brand }} you need.
</p> </p>
</div> </div>

View file

@ -137,8 +137,8 @@ class TestInstanceBrand:
assert "Set up the Foundry AI CLI on this machine." in joined assert "Set up the Foundry AI CLI on this machine." in joined
# Step 2 is a pwd-check now (no auto-mkdir); brand + workspace_dir # Step 2 is a pwd-check now (no auto-mkdir); brand + workspace_dir
# thread through the warning copy + expected-path string. # thread through the warning copy + expected-path string.
assert "$HOME/FoundryAI" in joined assert "$HOME/Desktop/FoundryAI" in joined
assert "mkdir -p ~/FoundryAI && cd ~/FoundryAI" in joined assert "mkdir -p ~/Desktop/FoundryAI && cd ~/Desktop/FoundryAI" in joined
assert "Bootstrap your Foundry AI workspace" in joined assert "Bootstrap your Foundry AI workspace" in joined
assert "Foundry AI workspace is ready" in joined assert "Foundry AI workspace is ready" in joined
# No raw placeholders survive substitution. # No raw placeholders survive substitution.
@ -148,15 +148,15 @@ class TestInstanceBrand:
def test_default_brand_keeps_agnes_branding(self, tmp_path, monkeypatch): def test_default_brand_keeps_agnes_branding(self, tmp_path, monkeypatch):
"""Backwards-compat: callers that don't pass brand/workspace_dir """Backwards-compat: callers that don't pass brand/workspace_dir
get the literal 'Agnes' / '~/Agnes' rendering.""" get the literal 'Agnes' / '~/Desktop/Agnes' rendering."""
mod = self._reload(tmp_path, monkeypatch) mod = self._reload(tmp_path, monkeypatch)
from app.web.setup_instructions import resolve_lines from app.web.setup_instructions import resolve_lines
joined = "\n".join(resolve_lines("agnes.whl")) joined = "\n".join(resolve_lines("agnes.whl"))
assert "Set up the Agnes CLI on this machine." in joined assert "Set up the Agnes CLI on this machine." in joined
# Step 2 is a pwd-check (no auto-mkdir); default path threads # Step 2 is a pwd-check (no auto-mkdir); default path threads
# through as `$HOME/Agnes` + the warning's manual-mkdir example. # through as `$HOME/Desktop/Agnes` + the warning's manual-mkdir example.
assert "$HOME/Agnes" in joined assert "$HOME/Desktop/Agnes" in joined
assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined assert "mkdir -p ~/Desktop/Agnes && cd ~/Desktop/Agnes" in joined
assert "Bootstrap your Agnes workspace" in joined assert "Bootstrap your Agnes workspace" in joined
assert "Agnes workspace is ready" in joined assert "Agnes workspace is ready" in joined
mod._instance_config = None mod._instance_config = None

View file

@ -921,7 +921,7 @@ def test_restart_claude_step_emitted_unconditionally():
def test_restart_claude_substitutes_workspace_dir(): def test_restart_claude_substitutes_workspace_dir():
"""The restart-claude body interpolates the workspace folder so the """The restart-claude body interpolates the workspace folder so the
user sees their actual `~/<brand>` path, not a literal placeholder.""" user sees their actual `~/Desktop/<brand>` path, not a literal placeholder."""
from app.web.setup_instructions import resolve_lines from app.web.setup_instructions import resolve_lines
joined = "\n".join(resolve_lines( joined = "\n".join(resolve_lines(
@ -930,7 +930,7 @@ def test_restart_claude_substitutes_workspace_dir():
workspace_dir="FoundryAI", workspace_dir="FoundryAI",
)) ))
assert "9) Restart Claude Code" in joined assert "9) Restart Claude Code" in joined
assert "~/FoundryAI" in joined assert "~/Desktop/FoundryAI" in joined
assert "{workspace_dir}" not in joined assert "{workspace_dir}" not in joined
@ -1030,12 +1030,12 @@ def test_step_2_checks_pwd_does_not_auto_mkdir():
# `pwd` check + expected-path comparison must be present. # `pwd` check + expected-path comparison must be present.
assert "pwd" in joined assert "pwd" in joined
assert "$HOME/Agnes" in joined # default workspace_dir assert "$HOME/Desktop/Agnes" in joined # default workspace_dir under Desktop
assert "~/Agnes" in joined assert "~/Desktop/Agnes" in joined
# Warning copy that references the /home Step 2 manual mkdir line. # Warning copy that references the /home Step 2 manual mkdir line.
assert "/home Step 2" in joined assert "/home Step 2" in joined
assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined assert "mkdir -p ~/Desktop/Agnes && cd ~/Desktop/Agnes" in joined
# Explicit "install here" / "abort" decision tree. # Explicit "install here" / "abort" decision tree.
assert "'install here'" in joined assert "'install here'" in joined
@ -1060,13 +1060,13 @@ def test_step_2_warning_substitutes_custom_brand():
workspace_dir="FoundryAI", workspace_dir="FoundryAI",
)) ))
assert "2) Verify the user is already in the workspace folder." in joined assert "2) Verify the user is already in the workspace folder." in joined
assert "$HOME/FoundryAI" in joined assert "$HOME/Desktop/FoundryAI" in joined
assert "~/FoundryAI" in joined assert "~/Desktop/FoundryAI" in joined
assert "mkdir -p ~/FoundryAI && cd ~/FoundryAI" in joined assert "mkdir -p ~/Desktop/FoundryAI && cd ~/Desktop/FoundryAI" in joined
# Brand + workspace_dir thread through the warning copy (the text # Brand + workspace_dir thread through the warning copy (the text
# wraps across two lines so we check the substrings separately). # wraps across two lines so we check the substrings separately).
assert "but Foundry AI is normally" in joined assert "but Foundry AI is normally" in joined
assert "installed in ~/FoundryAI" in joined assert "installed in ~/Desktop/FoundryAI" in joined
# No placeholders survive into the rendered text. # No placeholders survive into the rendered text.
assert "{workspace_dir}" not in joined assert "{workspace_dir}" not in joined
assert "{instance_brand}" not in joined assert "{instance_brand}" not in joined
@ -1084,7 +1084,7 @@ def test_step_9_restart_references_install_dir_not_hardcoded():
# Wording references the step-2 confirmation. # Wording references the step-2 confirmation.
assert "install dir confirmed in step 2" in joined assert "install dir confirmed in step 2" in joined
# Default path still mentioned as the expected baseline. # Default path still mentioned as the expected baseline.
assert "~/Agnes" in joined assert "~/Desktop/Agnes" in joined
# The "install here" callout in the restart step keeps the user-flow # The "install here" callout in the restart step keeps the user-flow
# connection visible. # connection visible.
assert "'install here'" in joined assert "'install here'" in joined