feat(home): drop /home connectors block — onboarding covers it (#305)

The dedicated `<details data-section="connectors">` section on /home
duplicated content that the install hero's Step 4 clipboard payload
already inlines. Both surfaces sourced the same prompt strings from
`app/web/connector_prompts.py` (home tiles via `<code id="*-prompt">`,
setup script via `app/web/setup_instructions.py::_connectors_block`),
so users walking the install script visited each connector inline and
then had no reason to scroll back up.

Removed the full block (3 tiles + summary + section-label). Lead
paragraph in the install hero now mentions the connector families
briefly so the benefit is visible before kick-off:

  "... your team's curated data, plugins, third-party tools (Asana,
   Google Workspace, Atlassian), and shared knowledge ... the install
   script also connects your tools for you, so there's no extra page
   to visit."

The "Email admin" mailto CTA, previously gated inside the GWS tile
when admin_email was set + GWS unconfigured, moves implicitly to the
install script's GWS step (Claude prompts the user when the OAuth
gating wall lands). Tests updated:

- test_connectors_section_removed_from_home (renamed from
  test_connectors_render_flat_when_onboarded_by_default) — asserts
  `class="connector-tiles"` and `data-section="connectors"` are absent
  in BOTH onboarded states, and that the lead paragraph still mentions
  the three connector families so the benefit isn't lost.
- test_home_renders_connector_prompts_from_shared_module — DROPPED.
  Was a parity check between the home tiles and the setup script's
  connector_prompts.py source. One surface now → no drift risk → test
  redundant. Replaced with an inline comment pointing future readers
  at where the strings flow (setup_instructions.py::_connectors_block).
- test_home_no_longer_shows_email_admin_button (renamed from
  test_home_shows_email_admin_button_when_admin_email_set_and_gws_unconfigured)
  — asserts the mailto CTA is gone from /home regardless of
  admin_email / GWS-configured state; documents the path-move.

CSS for `.connector-tile*` left in place as dead bytes — small
footprint, no behavior, easy follow-up if/when someone audits.
This commit is contained in:
Vojtech 2026-05-14 20:31:24 +04:00 committed by GitHub
parent 3e19caa975
commit d63f1473ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 176 deletions

View file

@ -1453,7 +1453,7 @@
<div class="eyebrow">Welcome, {{ display_name }} — let's get you set up</div> <div class="eyebrow">Welcome, {{ display_name }} — let's get you set up</div>
<h1>Connect Claude Code on your machine to your team's data</h1> <h1>Connect Claude Code on your machine to your team's data</h1>
<p class="lead"> <p class="lead">
{{ instance_brand }} gives <strong>Claude Code</strong> on your computer access to your team's <strong>curated data, plugins, and shared knowledge</strong> — so you can ask questions and get answers in plain language, right from your terminal. This page walks you through the <strong>one-time setup (~10 minutes)</strong>. Everything it installs lives in your home folder (<code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">~/{{ workspace_dir }}</code>) and can be removed in one command. {{ instance_brand }} gives <strong>Claude Code</strong> on your computer access to your team's <strong>curated data, plugins, third-party tools (Asana, Google Workspace, Atlassian), and shared knowledge</strong> — so you can ask questions and get answers in plain language, right from your terminal. This page walks you through the <strong>one-time setup (~10 minutes)</strong>; the install script also connects your tools for you, so there's no extra page to visit. Everything it installs lives in your home folder (<code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">~/{{ workspace_dir }}</code>) and can be removed in one command.
</p> </p>
<div class="install-block"> <div class="install-block">
@ -1662,99 +1662,17 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
<p class="home-usage-foot">For the deepest integration, create every project under <code>~/{{ workspace_dir }}/Projects/</code> — existing or new. The bundled plugin keeps each project in sync with the central catalog automatically, and the session-analysis loop is scoped to that root.</p> <p class="home-usage-foot">For the deepest integration, create every project under <code>~/{{ workspace_dir }}/Projects/</code> — existing or new. The bundled plugin keeps each project in sync with the central catalog automatically, and the session-analysis loop is scoped to that root.</p>
</section> </section>
<details class="setup-collapsible" data-section="connectors" open> {# Connectors `<details data-section="connectors">` block removed —
<summary> the install-hero's Step 4 clipboard payload (rendered via
<span class="ico" aria-hidden="true">&#x1F517;</span> `_claude_setup_instructions.jinja` inside the "Or paste manually"
<span class="ttl">Connect your tools <small>(Asana / Google Workspace / Atlassian)</small></span> fallback) already inlines the same Asana / GWS / Atlassian
<span class="chev" aria-hidden="true">&rsaquo;</span> prompts from app/web/connector_prompts.py via
</summary> app/web/setup_instructions.py::_connectors_block. Showing them
<div class="section-label section-label-flat">Once {{ instance_brand }} is installed — connect your tools</div> a second time as standalone cards duplicated UX without adding
reach — the install script visits them all in sequence. Brief
<div class="connector-tiles"> mention in the install-hero lead paragraph above covers the
<div class="connector-tile"> benefits ("third-party tools (Asana, Google Workspace,
<span class="ico">&#x2705;</span> Atlassian)"); deep ops live in /setup-advanced. #}
{# P0-3 — title row + time-badge + post-copy hint. The hint
container is rendered hidden; the JS reveals it after the
copy succeeds, then auto-hides after 8 s. #}
<div class="ttl-row">
<span class="ttl">Asana</span>
<span class="time-badge">~5 min · self-serve</span>
</div>
<div class="desc">Read tasks and projects, comment, create updates — Claude works alongside your project boards without leaving the terminal.</div>
<div class="connector-actions">
<button class="connector-copy" data-copy-target="asana-prompt" data-connector="Asana">Copy prompt</button>
<div class="copy-next-hint" data-hint-for="Asana">
<span>&#x2705; Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste &amp; press Enter.</span>
</div>
<details class="connector-preview">
<summary>Show prompt</summary>
{# Asana prompt body sourced from app/web/connector_prompts.py
(`asana_prompt()`). Same string the main setup script
inlines in step 9, so the two surfaces stay in lockstep. #}
<div class="card-mini-cmd"><code id="asana-prompt">{{ connector_prompts.asana }}</code></div>
</details>
</div>
</div>
<div class="connector-tile">
<span class="ico">&#x1F4DA;</span>
{# P0-3 + P1-8 — Google Workspace tile. Time badge warns when
the operator hasn't provisioned a shared OAuth app (forces
the user to set up GCP themselves, which is a ~20-min
clickops detour). The gating-note + email-admin button
appear in that same un-configured branch so the user has
a way out before copying. #}
<div class="ttl-row">
<span class="ttl">Google Workspace</span>
{% if gws_oauth.configured %}
<span class="time-badge">~5 min · self-serve</span>
{% else %}
<span class="time-badge is-warn">~20 min · admin help likely</span>
{% endif %}
</div>
<div class="desc">Drive, Calendar, Gmail, Docs, Sheets, Chat — Claude reads and acts across your work account via the official <code>gws</code> CLI.</div>
{% if not gws_oauth.configured %}
<div class="gating-note">
<strong>Heads up:</strong> your {{ instance_brand }} admin hasn't provisioned a shared Google Cloud OAuth app yet, so this connector needs GCP project setup (creating an OAuth client, enabling APIs). It's fastest to ask your admin first — the button below pre-fills the email.
</div>
{% endif %}
<div class="connector-actions">
<button class="connector-copy" data-copy-target="gws-prompt" data-connector="Google Workspace">Copy prompt</button>
{% if not gws_oauth.configured and instance_admin_email %}
<a class="email-admin" href="mailto:{{ instance_admin_email }}?subject={{ instance_brand|urlencode }}%20%E2%80%94%20Google%20Workspace%20OAuth%20setup&amp;body=Hi%20%E2%80%94%20I'd%20like%20to%20connect%20Google%20Workspace%20in%20{{ instance_brand|urlencode }}%20but%20it%20looks%20like%20a%20shared%20OAuth%20app%20isn't%20provisioned%20yet.%20Could%20you%20set%20it%20up%20for%20our%20instance%3F%20Thanks!">
&#x2709;&#xFE0F; Email admin
</a>
{% endif %}
<div class="copy-next-hint" data-hint-for="Google Workspace">
<span>&#x2705; Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste &amp; press Enter.</span>
</div>
<details class="connector-preview">
<summary>Show prompt</summary>
<div class="card-mini-cmd"><code id="gws-prompt">{{ connector_prompts.gws }}</code></div>
</details>
</div>
</div>
<div class="connector-tile">
<span class="ico">&#x1F39F;&#xFE0F;</span>
<div class="ttl-row">
<span class="ttl">Atlassian (Jira / Confluence)</span>
<span class="time-badge">~7 min · self-serve</span>
</div>
<div class="desc">Read and write Jira issues, search Confluence pages — Claude pulls ticket context and posts updates without leaving the workspace.</div>
<div class="connector-actions">
<button class="connector-copy" data-copy-target="jira-prompt" data-connector="Atlassian">Copy prompt</button>
<div class="copy-next-hint" data-hint-for="Atlassian">
<span>&#x2705; Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste &amp; press Enter.</span>
</div>
<details class="connector-preview">
<summary>Show prompt</summary>
<div class="card-mini-cmd"><code id="jira-prompt">{{ connector_prompts.atlassian }}</code></div>
</details>
</div>
</div>
</div>
</details>
<div class="section-label">Want to look around first?</div> <div class="section-label">Want to look around first?</div>
<p class="look-around-lead">You don't need {{ instance_brand }} installed locally to browse what's available. Anything you bookmark or subscribe to will be there waiting after you set {{ instance_brand }} up.</p> <p class="look-around-lead">You don't need {{ instance_brand }} installed locally to browse what's available. Anything you bookmark or subscribe to will be there waiting after you set {{ instance_brand }} up.</p>

View file

@ -109,20 +109,20 @@ def test_home_onboarded_user_sees_nav_hub(fresh_db):
assert "Step 4 — install" not in body assert "Step 4 — install" not in body
def test_connectors_render_flat_when_onboarded_by_default(fresh_db): def test_connectors_section_removed_from_home(fresh_db):
"""Connect-your-tools section must NOT auto-collapse on the """The dedicated `<details data-section="connectors">` block was
server-side `users.onboarded=TRUE` flip. It renders flat (in <details dropped from `/home` the install-hero's Step 4 clipboard payload
open>) by default; only an explicit user click on the in-hero (rendered via `_claude_setup_instructions.jinja` inside the manual
"Minimize setup view" toggle (persisted in localStorage, not server) fallback) already inlines the same Asana / GWS / Atlassian prompts
activates the collapsed bar layout. from `app/web/connector_prompts.py` via
`app/web/setup_instructions.py::_connectors_block`. Showing them
twice on the same page was duplicate UX. The lead paragraph in the
install-hero now mentions the connectors briefly so users still see
the benefit before they hit the install.
Auto-mode used to be a peer `setup-collapsible` section Co-asserts the auto-mode block removal that this test originally
(`data-section="step3"`) outside the install-hero. It moved into the pinned onboarded users still see neither the connectors block
install-hero as Step 2 of the install flow (so users enable it nor the legacy auto-mode peer section."""
BEFORE Step 3's ~20-command install runs), and the standalone
outside-hero copy was dropped to avoid duplicating reference
content. Onboarded users no longer see the auto-mode block at all
consistent with Step 1 + Step 3 also hiding post-onboarding."""
from src.db import get_system_db, close_system_db from src.db import get_system_db, close_system_db
conn = get_system_db() conn = get_system_db()
@ -136,22 +136,37 @@ def test_connectors_render_flat_when_onboarded_by_default(fresh_db):
resp = c.get("/home", cookies={"access_token": sess}) resp = c.get("/home", cookies={"access_token": sess})
assert resp.status_code == 200 assert resp.status_code == 200
body = resp.text body = resp.text
# Auto-mode no longer renders for onboarded users — both the # Auto-mode peer section still gone (legacy guard, not regressed).
# in-hero install-block and the legacy outside-hero `<details>`
# reference card are gated `{% if not onboarded %}` / removed.
assert 'class="automode-card"' not in body assert 'class="automode-card"' not in body
assert 'data-section="step3"' not in body assert 'data-section="step3"' not in body
assert "Step 2 — turn on auto-mode" not in body assert "Step 2 — turn on auto-mode" not in body
# Connect-your-tools section is still flat-open by default. # Dedicated connectors block is gone from /home in BOTH states.
assert 'class="connector-tiles"' in body assert 'class="connector-tiles"' not in body
assert 'class="setup-collapsible" data-section="connectors" open' in body assert 'data-section="connectors"' not in body
# Server-rendered HTML never carries the data-setup-minimized # Server-rendered HTML never carries the data-setup-minimized
# attribute on the .home-mock root — that's a client-side # attribute on the .home-mock root — that's a client-side
# localStorage decision applied via JS on load. The token still # localStorage decision applied via JS on load.
# appears in inline CSS selectors and the JS body, which is fine.
assert '<div class="home-mock" data-setup-minimized' not in body assert '<div class="home-mock" data-setup-minimized' not in body
assert 'class="home-mock"\n' in body or '<div class="home-mock">' in body assert 'class="home-mock"\n' in body or '<div class="home-mock">' in body
# Not-onboarded path: same — the section disappears regardless of
# state. Lead-paragraph still surfaces the connector names so users
# know the benefit exists before they kick off the install.
conn = get_system_db()
try:
_, sess2 = _make_user_and_session(
conn, email="not-onboarded@example.com", onboarded=False
)
finally:
conn.close()
close_system_db()
body2 = _client().get("/home", cookies={"access_token": sess2}).text
assert 'class="connector-tiles"' not in body2
assert 'data-section="connectors"' not in body2
# Lead-paragraph mentions the three connector families so the
# benefit isn't lost when the dedicated section disappears.
assert "Asana, Google Workspace, Atlassian" in body2
def test_minimize_toggle_no_longer_rendered(fresh_db): def test_minimize_toggle_no_longer_rendered(fresh_db):
"""The "Minimize setup view" toggle used to live inside the """The "Minimize setup view" toggle used to live inside the
@ -237,12 +252,15 @@ def test_home_hides_email_admin_button_when_admin_email_unset(fresh_db, monkeypa
assert "mailto:?" not in body # specifically, no broken empty mailto assert "mailto:?" not in body # specifically, no broken empty mailto
def test_home_shows_email_admin_button_when_admin_email_set_and_gws_unconfigured( def test_home_no_longer_shows_email_admin_button(fresh_db, monkeypatch):
fresh_db, monkeypatch, """The Email-admin mailto CTA used to live inside the /home GWS
): connector tile. With the dedicated `<details data-section="connectors">`
"""When admin_email is set AND gws_oauth is unconfigured, the mailto block removed (see test_connectors_section_removed_from_home above),
link renders. (Both conditions required see template guard the button has no rendering site even when admin_email is set + GWS
``{% if not gws_oauth.configured and instance_admin_email %}``.)""" is unconfigured. The escalation path lives inside the install
script's GWS step now — Claude prompts the user with the admin
email when the connector setup hits an OAuth gating wall, so the
affordance moves to the surface where it's actually useful."""
monkeypatch.setenv("AGNES_INSTANCE_ADMIN_EMAIL", "ops@example.com") monkeypatch.setenv("AGNES_INSTANCE_ADMIN_EMAIL", "ops@example.com")
monkeypatch.delenv("AGNES_GWS_CLIENT_ID", raising=False) monkeypatch.delenv("AGNES_GWS_CLIENT_ID", raising=False)
monkeypatch.delenv("AGNES_GWS_CLIENT_SECRET", raising=False) monkeypatch.delenv("AGNES_GWS_CLIENT_SECRET", raising=False)
@ -254,8 +272,8 @@ def test_home_shows_email_admin_button_when_admin_email_set_and_gws_unconfigured
conn.close() conn.close()
close_system_db() close_system_db()
body = _client().get("/home", cookies={"access_token": sess}).text body = _client().get("/home", cookies={"access_token": sess}).text
assert "Email admin" in body assert "Email admin" not in body
assert "mailto:ops@example.com" in body assert 'mailto:ops@example.com' not in body
def test_home_hides_email_admin_button_when_gws_configured(fresh_db, monkeypatch): def test_home_hides_email_admin_button_when_gws_configured(fresh_db, monkeypatch):
@ -276,59 +294,17 @@ def test_home_hides_email_admin_button_when_gws_configured(fresh_db, monkeypatch
assert "Email admin" not in body assert "Email admin" not in body
def test_home_renders_connector_prompts_from_shared_module(fresh_db): # `test_home_renders_connector_prompts_from_shared_module` was dropped here
"""Single source of truth check: the prompt text the /home tiles # alongside the removal of the /home `<details data-section="connectors">`
paste must equal the strings ``app/web/connector_prompts.py`` returns. # block. The test pinned source-of-truth parity between the home tile
The same strings are also inlined into the setup script's step 9, so # `<code id="*-prompt">` blocks and `app/web/connector_prompts.py`. With the
if they ever drift the two surfaces would tell users to do different # tiles gone, the only surface left for those strings is the install-hero's
things this test catches that early.""" # Step 4 clipboard payload (rendered via `_claude_setup_instructions.jinja`
import html as _html # from `setup_instructions_lines`, which is built in
import re # `app/web/setup_instructions.py::_connectors_block` calling the same
# `connector_prompts.py` functions). One surface, no drift risk → the
from src.db import get_system_db, close_system_db # parity test is redundant. If a second surface ever re-renders these
from app.web.connector_prompts import ( # prompts, restore a parity test scoped to that new consumer.
asana_prompt, gws_prompt, atlassian_prompt,
)
from app.instance_config import (
get_gws_oauth_credentials, get_instance_admin_email,
)
conn = get_system_db()
try:
_, sess = _make_user_and_session(conn)
finally:
conn.close()
close_system_db()
c = _client()
body = c.get("/home", cookies={"access_token": sess}).text
# Resolve the same gws_oauth dict the route uses so the parity check
# exercises whichever branch (configured / manual) is active in the
# current test environment.
gws = get_gws_oauth_credentials()
expected_gws = gws_prompt(
gws_oauth_configured=bool(gws.get("configured")),
gws_client_id=str(gws.get("client_id") or ""),
gws_client_secret=str(gws.get("client_secret") or ""),
gws_project_id=str(gws.get("project_id") or ""),
oauthlib_insecure_transport=str(gws.get("oauthlib_insecure_transport") or "1"),
instance_admin_email=get_instance_admin_email(),
)
for slug, expected in (
("asana", asana_prompt()),
("gws", expected_gws),
("jira", atlassian_prompt()),
):
m = re.search(rf'<code id="{slug}-prompt">(.*?)</code>', body, re.DOTALL)
assert m, f"{slug}-prompt block missing from /home"
actual = _html.unescape(m.group(1))
assert actual == expected, (
f"{slug}-prompt body diverged from connector_prompts module — "
f"the home tile and setup script will paste different text. "
f"len(home)={len(actual)} len(module)={len(expected)}"
)
# ── Getting Started + Overview + Usage modes (PR #289 home additions) ──── # ── Getting Started + Overview + Usage modes (PR #289 home additions) ────