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:
parent
3e19caa975
commit
d63f1473ab
2 changed files with 70 additions and 176 deletions
|
|
@ -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">🔗</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">›</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">✅</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>✅ Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste & 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">📚</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&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!">
|
|
||||||
✉️ Email admin
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<div class="copy-next-hint" data-hint-for="Google Workspace">
|
|
||||||
<span>✅ Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste & 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">🎟️</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>✅ Copied. Now paste into Claude Code — run <code>claude</code> in your terminal, then paste & 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>
|
||||||
|
|
|
||||||
|
|
@ -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) ────
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue