From 0b292dbd3e99db8132800e9d104c7c10a90d9e0b Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 21:24:43 +0200 Subject: [PATCH 01/21] =?UTF-8?q?fix:=20declutter=20frontpage=20=E2=80=94?= =?UTF-8?q?=20remove=20stats=20row=20and=20recent=20plans=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove credits/plans stats row - Remove Recent Plans list (available via /plan) - Rename "Start a New Plan" to "Create a New Plan" Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 70 +----------------------- 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 1ee9ae5d..259a34db 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -145,43 +145,6 @@ font-size: 0.95rem; } - .stats-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 16px; - margin-bottom: 32px; - } - .stat-card { - background: var(--color-bg-soft); - border: 1px solid var(--color-border); - border-radius: var(--radius-lg); - padding: 20px 24px; - text-align: center; - } - .stat-value { - font-size: 1.75rem; - font-weight: 800; - color: var(--color-text); - line-height: 1.2; - } - .stat-value.credits { - color: #059669; - } - .stat-card-link { - display: block; - text-decoration: none; - color: inherit; - transition: border-color 0.15s, box-shadow 0.15s; - } - .stat-card-link:hover { - border-color: var(--color-primary); - box-shadow: 0 0 0 1px var(--color-primary); - } - .stat-card-link .stat-value.credits { - text-decoration: underline; - text-underline-offset: 3px; - text-decoration-color: rgba(5, 150, 105, 0.4); - } .stat-card-link:hover .stat-value.credits { text-decoration-color: var(--color-primary); } @@ -601,7 +564,6 @@ .hero { padding: 40px 0 32px; } .hero-actions { flex-direction: column; align-items: center; } .btn-hero-outline { margin-left: 0; } - .stats-row { grid-template-columns: 1fr; } } {% endblock %} @@ -614,22 +576,12 @@

Welcome back, {{ user.name or user.given_name or "there" }}

Your PlanExe dashboard — manage your plans and credits.

- {# ── Start New Plan form ────────────────────────────────────── #}
-

Start a New Plan

+

Create a New Plan

{% if example_prompts %} @@ -690,26 +642,6 @@

Start a New Plan

-{% if recent_tasks %} -

Recent Plans

- -{% else %} -
-

You haven't created any plans yet. Describe your idea above and click Generate Plan to get started.

-
-{% endif %} {% elif is_admin %} {# ─── Admin shortcut ────────────────────────────────────────── #} From d5cfd15632615cc74606231b120402387ce7066f Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 21:34:46 +0200 Subject: [PATCH 02/21] feat: add MCP recommendation below the Create a New Plan form Explains that the form works for quick tests but MCP produces better results by having the AI compose a detailed prompt. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 259a34db..0437de6d 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -642,6 +642,17 @@

Create a New Plan

+
+

+ Tip: This form works for quick tests, but PlanExe produces much better results when your AI assistant writes the prompt for you. A good prompt is detailed — it includes your goal, constraints, budget, timeline, and context. Writing all of that by hand takes effort and it's easy to leave out important details. +

+

+ The recommended way to use PlanExe is through MCP (Model Context Protocol). Connect PlanExe to your preferred AI tool — such as Claude, Cursor, or Windsurf — and let it interview you about your idea, then compose a thorough prompt on your behalf. This consistently produces higher-quality plans because the AI ensures nothing important is missing. +

+

+ See the documentation for how to set up MCP with your AI tool. +

+
{% elif is_admin %} {# ─── Admin shortcut ────────────────────────────────────────── #} From 57d4d59f28d23bf2a4bd72324b9a4320ccd44b21 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 21:36:56 +0200 Subject: [PATCH 03/21] fix: link to MCP docs at docs.planexe.org Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 0437de6d..4b70505b 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -650,7 +650,7 @@

Create a New Plan

The recommended way to use PlanExe is through MCP (Model Context Protocol). Connect PlanExe to your preferred AI tool — such as Claude, Cursor, or Windsurf — and let it interview you about your idea, then compose a thorough prompt on your behalf. This consistently produces higher-quality plans because the AI ensures nothing important is missing.

- See the documentation for how to set up MCP with your AI tool. + See the MCP documentation for how to set up MCP with your AI tool.

From ad8d68f7b7cfc9a7f3d5d748fe2c93dbb3ac07bb Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 21:38:32 +0200 Subject: [PATCH 04/21] fix: remove welcome greeting from frontpage Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 4b70505b..1e95ff1c 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -571,13 +571,7 @@ {% block content %} {% if user %} {# ─── Dashboard for signed-in users ─────────────────────────── #} -
-

Welcome back, {{ user.name or user.given_name or "there" }}

-

Your PlanExe dashboard — manage your plans and credits.

-
- - -{# ── Start New Plan form ────────────────────────────────────── #} +{# ── Create New Plan form ────────────────────────────────────── #}
From d8ff8ee0acc54ebca2557ea3e0a7c3a78aa696d9 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 21:41:38 +0200 Subject: [PATCH 05/21] fix: replace "This form works" with "You can type a prompt above" Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 1e95ff1c..5619e640 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -638,7 +638,7 @@

Create a New Plan

- Tip: This form works for quick tests, but PlanExe produces much better results when your AI assistant writes the prompt for you. A good prompt is detailed — it includes your goal, constraints, budget, timeline, and context. Writing all of that by hand takes effort and it's easy to leave out important details. + Tip: You can type a prompt above to try things out, but PlanExe produces much better results when your AI assistant writes the prompt for you. A good prompt is detailed — it includes your goal, constraints, budget, timeline, and context. Writing all of that by hand takes effort and it's easy to leave out important details.

The recommended way to use PlanExe is through MCP (Model Context Protocol). Connect PlanExe to your preferred AI tool — such as Claude, Cursor, or Windsurf — and let it interview you about your idea, then compose a thorough prompt on your behalf. This consistently produces higher-quality plans because the AI ensures nothing important is missing. From bdcef0ea6217dee093604983cacfc7d7ec1a8698 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 21:43:24 +0200 Subject: [PATCH 06/21] fix: update tip wording per user feedback Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 5619e640..033fe351 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -638,7 +638,7 @@

Create a New Plan

- Tip: You can type a prompt above to try things out, but PlanExe produces much better results when your AI assistant writes the prompt for you. A good prompt is detailed — it includes your goal, constraints, budget, timeline, and context. Writing all of that by hand takes effort and it's easy to leave out important details. + Tip: You can type a prompt above to try PlanExe out, however the intended way is when your AI assistant writes the prompt for you. A good prompt is detailed — it includes your goal, constraints, budget, timeline, and context. Writing all of that by hand takes effort and it's easy to leave out important details.

The recommended way to use PlanExe is through MCP (Model Context Protocol). Connect PlanExe to your preferred AI tool — such as Claude, Cursor, or Windsurf — and let it interview you about your idea, then compose a thorough prompt on your behalf. This consistently produces higher-quality plans because the AI ensures nothing important is missing. From eaf40c861127d9e49d6cc3d3b31b53ee09876148 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 21:54:24 +0200 Subject: [PATCH 07/21] feat: show example plans link for new users with no plans Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/plan_list.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frontend_multi_user/templates/plan_list.html b/frontend_multi_user/templates/plan_list.html index 155097c7..9c619593 100644 --- a/frontend_multi_user/templates/plan_list.html +++ b/frontend_multi_user/templates/plan_list.html @@ -166,6 +166,16 @@

Plans

{% endif %}
+{% if not plan_rows %} +
+

New here?

+

+ Take a look at some example reports to see what PlanExe generates — from business plans to infrastructure projects. +

+ Browse example plans → +
+{% endif %} + -{% endif %} -{% if user %} - -{% endif %} -{% if user %} - -{% endif %} {% endblock %} diff --git a/frontend_multi_user/templates/plan_create.html b/frontend_multi_user/templates/plan_create.html new file mode 100644 index 00000000..74ad5391 --- /dev/null +++ b/frontend_multi_user/templates/plan_create.html @@ -0,0 +1,267 @@ +{% extends "base.html" %} +{% block title %}Create a New Plan - PlanExe{% endblock %} +{% block head %} + +{% endblock %} + +{% block content %} +
+
+ +

Create a New Plan

+
+
+ {% if example_prompts %} +
+ {% for ep in example_prompts %} + + {% endfor %} +
+ {% endif %} +
+ + + + + +
+
+
+ +
+

+ Tip: You can type a prompt above to try PlanExe out, however the intended way is when your AI assistant writes the prompt for you. A good prompt is detailed — it includes your goal, constraints, budget, timeline, and context. Writing all of that by hand takes effort and it's easy to leave out important details. +

+

+ The recommended way to use PlanExe is through MCP (Model Context Protocol). Connect PlanExe to your preferred AI tool — such as Claude, Cursor, or Windsurf — and let it interview you about your idea, then compose a thorough prompt on your behalf. This consistently produces higher-quality plans because the AI ensures nothing important is missing. +

+

+ See the MCP documentation for how to set up MCP with your AI tool. +

+
+ +{% if example_prompts %} + +{% endif %} + +{% endblock %} From 97f3b33213db57148d104107a19030fb439b3706 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 22:24:31 +0200 Subject: [PATCH 10/21] feat: onboarding dashboard on frontpage + move form to /plan/create Frontpage shows 5-step getting-started checklist: 1. Create account (always done) 2. Deposit credits (checks balance and transactions) 3. Create API key (counts active keys) 4. Connect via MCP (checks LLM call count) 5. Superuser badge (5+ plans created) Navbar: Home, Create, Plans, Models. Create form moved to /plan/create with its own template. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/src/app.py | 166 +++++++++++------------ frontend_multi_user/templates/index.html | 116 +++++++++++++--- 2 files changed, 179 insertions(+), 103 deletions(-) diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index b96fade4..6325190a 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1038,113 +1038,109 @@ def inject_current_user_name(): @self.app.route('/') def index(): user = None - recent_tasks: list[SimpleNamespace] = [] - total_tasks_count = 0 is_admin = False - nonce = None - user_id = None - can_create_plan = False - example_prompts: list[str] = [] - credits_balance_display = "0" + onboarding_steps: list[dict] = [] + if current_user.is_authenticated: is_admin = current_user.is_admin try: if is_admin: admin_account = _get_current_user_account() - user_id = str(admin_account.id) if admin_account else self.admin_username user = SimpleNamespace(name="Admin", given_name=None) - credits_balance_display = "Full access" - can_create_plan = True + user_id = str(admin_account.id) if admin_account else self.admin_username else: user_uuid = uuid.UUID(str(current_user.id)) user = self.db.session.get(UserAccount, user_uuid) - if user: - user_id = str(user.id) + user_id = str(user.id) if user else None + + if user and user_id: + # Step 1: Account created (always done if logged in) + onboarding_steps.append({ + "title": "Create account", + "done": True, + "detail": "Signed in", + "link": None, + }) + + # Step 2: Deposit credits + if is_admin: + has_credits = True + credit_detail = "Full access" + else: credits_balance = to_credit_decimal(user.credits_balance) - credits_balance_display = format_credit_display(user.credits_balance) - min_credits = Decimal(os.environ.get("PLANEXE_MIN_CREDITS_TO_CREATE_PLAN", "2")) - can_create_plan = credits_balance >= min_credits - - if user_id: - # Generate a nonce so the user can start a plan from the dashboard - nonce = 'DASH_' + str(uuid.uuid4()) + tx_count = CreditHistory.query.filter_by(user_id=user.id).count() + has_credits = credits_balance > 0 or tx_count > 1 + credit_detail = format_credit_display(user.credits_balance) if has_credits else "No credits yet" + onboarding_steps.append({ + "title": "Deposit credits", + "done": has_credits, + "detail": credit_detail, + "link": url_for('account') if not has_credits else None, + }) + + # Step 3: Create API key + key_count = UserApiKey.query.filter_by(user_id=user.id if not is_admin else admin_account.id, revoked_at=None).count() if not is_admin else UserApiKey.query.filter_by(user_id=admin_account.id, revoked_at=None).count() + has_key = key_count >= 1 + if key_count == 0: + key_detail = "No API keys yet" + elif key_count == 1: + key_detail = "1 API key" + else: + key_detail = f"{key_count} API keys" + onboarding_steps.append({ + "title": "Create API key", + "done": has_key, + "detail": key_detail, + "link": url_for('account') if not has_key else None, + }) + + # Step 4: Use MCP (check if any API key has LLM calls) + total_llm_calls = 0 + if has_key: + user_key_ids = [ + str(k.id) for k in UserApiKey.query + .filter_by(user_id=user.id if not is_admin else admin_account.id, revoked_at=None) + .all() + ] + if user_key_ids: + try: + total_llm_calls = ( + self.db.session.query(func.count(TokenMetrics.id)) + .filter(TokenMetrics.api_key_id.in_(user_key_ids)) + .scalar() or 0 + ) + except Exception: + self.db.session.rollback() + used_mcp = total_llm_calls >= 1 + onboarding_steps.append({ + "title": "Connect via MCP", + "done": used_mcp, + "detail": f"{total_llm_calls} LLM calls" if used_mcp else "Not connected yet", + "link": "https://docs.planexe.org/mcp/mcp_welcome/" if not used_mcp else None, + }) + + # Step 5: Create 5+ plans uid_filter = ( PlanItem.user_id.in_(_admin_user_ids()) if is_admin else PlanItem.user_id == str(user_id) ) - try: - recent_task_rows = ( - self.db.session.query( - PlanItem.id, - PlanItem.state, - PlanItem.stop_requested, - func.substr(PlanItem.prompt, 1, 240).label("prompt_preview"), - ) - .filter(uid_filter) - .order_by(PlanItem.timestamp_created.desc()) - .limit(10) - .all() - ) - except DataError: - self.db.session.rollback() - logger.warning( - "Detected invalid UTF-8 in task_item.prompt for user_id=%s while loading dashboard; " - "falling back without prompt previews.", - user_id, - exc_info=True, - ) - recent_task_rows = ( - self.db.session.query( - PlanItem.id, - PlanItem.state, - PlanItem.stop_requested, - ) - .filter(uid_filter) - .order_by(PlanItem.timestamp_created.desc()) - .limit(10) - .all() - ) - recent_tasks = [] - for task in recent_task_rows: - prompt_preview = getattr(task, "prompt_preview", None) - if prompt_preview is None: - from src.plan_routes import _load_prompt_preview_safe - prompt_text = _load_prompt_preview_safe(task.id) - else: - prompt_text = (prompt_preview or "").strip() or "[Prompt unavailable]" - state = task.state if isinstance(task.state, PlanState) else None - recent_tasks.append( - SimpleNamespace( - id=str(task.id), - state=state, - prompt=prompt_text, - ) - ) - total_tasks_count = ( - PlanItem.query - .filter(uid_filter) - .count() - ) - # Load example prompts for the "Start New Plan" form - for prompt_uuid in DEMO_FORM_RUN_PROMPT_UUIDS: - prompt_item = self.prompt_catalog.find(prompt_uuid) - if prompt_item: - example_prompts.append(prompt_item.prompt) + total_plans = PlanItem.query.filter(uid_filter).count() + is_superuser = total_plans >= 5 + onboarding_steps.append({ + "title": "Superuser", + "done": is_superuser, + "detail": f"{total_plans} plans created" if is_superuser else f"{total_plans}/5 plans", + "link": None, + }) except Exception: logger.debug("Could not load dashboard data", exc_info=True) + return render_template( 'index.html', user=user, - credits_balance_display=credits_balance_display, - can_create_plan=can_create_plan, - total_tasks_count=total_tasks_count, - recent_tasks=recent_tasks, is_admin=is_admin, - nonce=nonce, - user_id=user_id, - example_prompts=example_prompts, - model_profile_options=_model_profile_options(), + onboarding_steps=onboarding_steps, ) @self.app.route('/models') diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 60ddbdec..11b3ff18 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -130,33 +130,89 @@ text-decoration: underline; } - /* ── Dashboard (signed in) ──────────────────────── */ - .dashboard-welcome { - margin-bottom: 32px; - } - .dashboard-welcome h1 { - font-size: 1.75rem; + /* ── Onboarding steps ──────────────────────────── */ + .onboarding-title { + font-size: 1.1rem; font-weight: 700; margin: 0 0 4px; } - .dashboard-welcome p { + .onboarding-subtitle { + font-size: 0.82rem; color: var(--color-text-secondary); + margin: 0 0 20px; + } + .onboarding-steps { + max-width: 480px; + } + .onboarding-step { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px 0; + border-bottom: 1px solid var(--color-border); + } + .onboarding-step:last-child { + border-bottom: none; + } + .onboarding-check { + flex-shrink: 0; + width: 22px; + height: 22px; + border-radius: 50%; + border: 2px solid var(--color-border); + display: flex; + align-items: center; + justify-content: center; + margin-top: 1px; + font-size: 0.7rem; + color: transparent; + } + .onboarding-check.done { + background: #059669; + border-color: #059669; + color: #fff; + } + .onboarding-check.done::after { + content: "\2713"; + } + .onboarding-step-body { + flex: 1; + min-width: 0; + } + .onboarding-step-title { + font-size: 0.88rem; + font-weight: 600; + color: var(--color-text); margin: 0; - font-size: 0.95rem; } - - .stat-card-link:hover .stat-value.credits { - text-decoration-color: var(--color-primary); + .onboarding-step.pending .onboarding-step-title { + color: var(--color-text-secondary); } - .stat-label { - font-size: 0.8rem; + .onboarding-step-detail { + font-size: 0.75rem; color: var(--color-text-secondary); - text-transform: uppercase; - letter-spacing: 0.05em; - font-weight: 600; - margin-top: 4px; + margin: 2px 0 0; + } + .onboarding-step-link { + font-size: 0.75rem; + color: var(--color-primary, #0066cc); + text-decoration: none; + } + .onboarding-step-link:hover { + text-decoration: underline; + } + .superuser-badge { + display: inline-block; + padding: 2px 8px; + background: #fef3c7; + border: 1px solid #f59e0b; + border-radius: 10px; + font-size: 0.7rem; + font-weight: 700; + color: #92400e; + margin-left: 6px; + vertical-align: middle; } - .quick-actions { display: flex; gap: 12px; @@ -226,6 +282,30 @@ {% block content %} {% if user %} {# ─── Dashboard for signed-in users ─────────────────────────── #} +

Getting Started

+

Connect PlanExe with your AI assistant to create plans.

+ +
+ {% for step in onboarding_steps %} +
+
+
+

+ {{ step.title }} + {% if step.title == "Superuser" and step.done %} + Superuser + {% endif %} +

+

+ {{ step.detail }} + {% if step.link %} + · {% if "docs.planexe" in step.link %}Setup guide{% else %}Go{% endif %} → + {% endif %} +

+
+
+ {% endfor %} +
{% elif is_admin %} {# ─── Admin shortcut ────────────────────────────────────────── #} From dd9472fc00ad212411765ad9c4fb0cee358ef92a Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 22:29:11 +0200 Subject: [PATCH 11/21] feat: add Discord support link below onboarding steps Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 11b3ff18..1f6b7a37 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -307,6 +307,10 @@

Getting Started

{% endfor %}
+

+ Need help? Join the PlanExe Discord for setup assistance and community support. +

+ {% elif is_admin %} {# ─── Admin shortcut ────────────────────────────────────────── #}
From 88daf84a1b52b6f6cda0209a0245d2ad70191444 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 23:21:47 +0200 Subject: [PATCH 12/21] feat: add ?debug=1 to show onboarding data on frontpage Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/src/app.py | 1 + frontend_multi_user/templates/index.html | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index 6325190a..db097d86 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1141,6 +1141,7 @@ def index(): user=user, is_admin=is_admin, onboarding_steps=onboarding_steps, + onboarding_debug=request.args.get("debug") == "1", ) @self.app.route('/models') diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 1f6b7a37..c16355f0 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -311,6 +311,18 @@

Getting Started

Need help? Join the PlanExe Discord for setup assistance and community support.

+{% if onboarding_debug %} +
+ Debug: onboarding data +
{% for step in onboarding_steps %}
+{{ step.title }}:
+  done: {{ step.done }}
+  detail: {{ step.detail }}
+  link: {{ step.link or "—" }}
+{% endfor %}
+
+{% endif %} + {% elif is_admin %} {# ─── Admin shortcut ────────────────────────────────────────── #}
From 88b460655afb6e757560381e4db5109e0f2ee960 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 23:31:42 +0200 Subject: [PATCH 13/21] feat: add per-step debug overrides for onboarding Use /?debug=1&step1=0&step2=1&step3=0&step4=0&step5=1 to force individual steps on/off for UI verification. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/src/app.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index db097d86..b1a65d2b 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1136,6 +1136,14 @@ def index(): except Exception: logger.debug("Could not load dashboard data", exc_info=True) + # Debug overrides: /?debug=1&step1=0&step2=1&step3=0&step4=1&step5=0 + if request.args.get("debug") == "1" and onboarding_steps: + step_keys = ["step1", "step2", "step3", "step4", "step5"] + for i, key in enumerate(step_keys): + val = request.args.get(key) + if val is not None and i < len(onboarding_steps): + onboarding_steps[i]["done"] = val == "1" + return render_template( 'index.html', user=user, From c776e158fc3355329f5613ff83069901f3f14bdf Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 13 Apr 2026 23:40:21 +0200 Subject: [PATCH 14/21] docs: add usage examples to onboarding debug panel Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index c16355f0..8436a5d6 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -319,7 +319,11 @@

Getting Started

done: {{ step.done }} detail: {{ step.detail }} link: {{ step.link or "—" }} -{% endfor %} +{% endfor %} +Override steps with URL parameters (1=done, 0=pending): + /?debug=1&step1=1&step2=1&step3=1&step4=1&step5=1 — all done + /?debug=1&step1=1&step2=0&step3=0&step4=0&step5=0 — new user + /?debug=1&step5=1 — force superuser {% endif %} From 62a5d8ea3e68efc20258896f7943a50e6f2c12a7 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 14 Apr 2026 00:14:37 +0200 Subject: [PATCH 15/21] fix: rename "Getting Started" to "Onboarding" Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 8436a5d6..2c43dd23 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -282,7 +282,7 @@ {% block content %} {% if user %} {# ─── Dashboard for signed-in users ─────────────────────────── #} -

Getting Started

+

Onboarding

Connect PlanExe with your AI assistant to create plans.

From e01c0e0fd57add2aa4ebf1c2c556024b4ae28469 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 14 Apr 2026 00:17:46 +0200 Subject: [PATCH 16/21] feat: add descriptions to onboarding steps Each incomplete step shows a short explanation of what to do. Descriptions are hidden once the step is completed. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/src/app.py | 5 +++++ frontend_multi_user/templates/index.html | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index b1a65d2b..7427da2a 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1057,6 +1057,7 @@ def index(): # Step 1: Account created (always done if logged in) onboarding_steps.append({ "title": "Create account", + "description": "Sign up for PlanExe to get started.", "done": True, "detail": "Signed in", "link": None, @@ -1073,6 +1074,7 @@ def index(): credit_detail = format_credit_display(user.credits_balance) if has_credits else "No credits yet" onboarding_steps.append({ "title": "Deposit credits", + "description": "Credits pay for the AI models that generate your plan. Go to Account to add credits.", "done": has_credits, "detail": credit_detail, "link": url_for('account') if not has_credits else None, @@ -1089,6 +1091,7 @@ def index(): key_detail = f"{key_count} API keys" onboarding_steps.append({ "title": "Create API key", + "description": "Your AI assistant uses an API key to connect to PlanExe. Create one on the Account page.", "done": has_key, "detail": key_detail, "link": url_for('account') if not has_key else None, @@ -1114,6 +1117,7 @@ def index(): used_mcp = total_llm_calls >= 1 onboarding_steps.append({ "title": "Connect via MCP", + "description": "Add PlanExe to your AI tool (Claude, Cursor, Windsurf, etc.) using your API key. Your AI will then be able to create plans for you.", "done": used_mcp, "detail": f"{total_llm_calls} LLM calls" if used_mcp else "Not connected yet", "link": "https://docs.planexe.org/mcp/mcp_welcome/" if not used_mcp else None, @@ -1129,6 +1133,7 @@ def index(): is_superuser = total_plans >= 5 onboarding_steps.append({ "title": "Superuser", + "description": "Create 5 or more plans to earn the Superuser badge.", "done": is_superuser, "detail": f"{total_plans} plans created" if is_superuser else f"{total_plans}/5 plans", "link": None, diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 2c43dd23..0f7b8104 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -188,6 +188,12 @@ .onboarding-step.pending .onboarding-step-title { color: var(--color-text-secondary); } + .onboarding-step-desc { + font-size: 0.78rem; + color: var(--color-text-secondary); + margin: 4px 0 0; + line-height: 1.5; + } .onboarding-step-detail { font-size: 0.75rem; color: var(--color-text-secondary); @@ -296,6 +302,9 @@

Onboarding

Superuser {% endif %}

+ {% if not step.done and step.description %} +

{{ step.description }}

+ {% endif %}

{{ step.detail }} {% if step.link %} From 56f1435d3d2b0a50f743479dd722e0ec4cec07a8 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 14 Apr 2026 00:26:10 +0200 Subject: [PATCH 17/21] fix: center onboarding content with equal margins Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 0f7b8104..01f813e8 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -288,6 +288,7 @@ {% block content %} {% if user %} {# ─── Dashboard for signed-in users ─────────────────────────── #} +

Onboarding

Connect PlanExe with your AI assistant to create plans.

@@ -319,6 +320,7 @@

Onboarding

Need help? Join the PlanExe Discord for setup assistance and community support.

+
{% if onboarding_debug %}
From 2ca90ab07f3224701727d1bee7bd64fa05711ba0 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 14 Apr 2026 00:29:38 +0200 Subject: [PATCH 18/21] fix: add links to Account page in onboarding step descriptions Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/src/app.py | 4 ++-- frontend_multi_user/templates/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index 7427da2a..41085473 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1074,7 +1074,7 @@ def index(): credit_detail = format_credit_display(user.credits_balance) if has_credits else "No credits yet" onboarding_steps.append({ "title": "Deposit credits", - "description": "Credits pay for the AI models that generate your plan. Go to Account to add credits.", + "description": 'Credits pay for the AI models that generate your plan. Go to Account to add credits.', "done": has_credits, "detail": credit_detail, "link": url_for('account') if not has_credits else None, @@ -1091,7 +1091,7 @@ def index(): key_detail = f"{key_count} API keys" onboarding_steps.append({ "title": "Create API key", - "description": "Your AI assistant uses an API key to connect to PlanExe. Create one on the Account page.", + "description": 'Your AI assistant uses an API key to connect to PlanExe. Create one on the Account page.', "done": has_key, "detail": key_detail, "link": url_for('account') if not has_key else None, diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 01f813e8..6bfbabc7 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -304,7 +304,7 @@

Onboarding

{% endif %}

{% if not step.done and step.description %} -

{{ step.description }}

+

{{ step.description | safe }}

{% endif %}

{{ step.detail }} From 6ce214f2d9e3dfed4f20a7f46c5ee07280ba1538 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 14 Apr 2026 00:33:05 +0200 Subject: [PATCH 19/21] fix: make Account links visible in onboarding descriptions Use primary color and bold weight, matching the Discord link style. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/templates/index.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend_multi_user/templates/index.html b/frontend_multi_user/templates/index.html index 6bfbabc7..56e85285 100644 --- a/frontend_multi_user/templates/index.html +++ b/frontend_multi_user/templates/index.html @@ -194,6 +194,14 @@ margin: 4px 0 0; line-height: 1.5; } + .onboarding-step-desc a { + color: var(--color-primary, #0066cc); + font-weight: 600; + text-decoration: none; + } + .onboarding-step-desc a:hover { + text-decoration: underline; + } .onboarding-step-detail { font-size: 0.75rem; color: var(--color-text-secondary); From 3cb1e82a1b6530d59ae86e84b467f352a1dfa33e Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 14 Apr 2026 00:37:43 +0200 Subject: [PATCH 20/21] fix: resolve pyright errors for admin_account possibly unbound Use a single account_id variable set once, instead of referencing admin_account in branches where it may not be bound. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/src/app.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index 41085473..3bc44f96 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1054,6 +1054,8 @@ def index(): user_id = str(user.id) if user else None if user and user_id: + account_id = admin_account.id if is_admin and admin_account else (user.id if hasattr(user, 'id') else None) + # Step 1: Account created (always done if logged in) onboarding_steps.append({ "title": "Create account", @@ -1081,7 +1083,7 @@ def index(): }) # Step 3: Create API key - key_count = UserApiKey.query.filter_by(user_id=user.id if not is_admin else admin_account.id, revoked_at=None).count() if not is_admin else UserApiKey.query.filter_by(user_id=admin_account.id, revoked_at=None).count() + key_count = UserApiKey.query.filter_by(user_id=account_id, revoked_at=None).count() if account_id else 0 has_key = key_count >= 1 if key_count == 0: key_detail = "No API keys yet" @@ -1102,9 +1104,9 @@ def index(): if has_key: user_key_ids = [ str(k.id) for k in UserApiKey.query - .filter_by(user_id=user.id if not is_admin else admin_account.id, revoked_at=None) + .filter_by(user_id=account_id, revoked_at=None) .all() - ] + ] if account_id else [] if user_key_ids: try: total_llm_calls = ( From 923a6db56adb52c6425aac450712ce306f6f945d Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 14 Apr 2026 00:39:44 +0200 Subject: [PATCH 21/21] fix: initialize admin_account to None to satisfy pyright Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend_multi_user/src/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend_multi_user/src/app.py b/frontend_multi_user/src/app.py index 3bc44f96..05d3c622 100644 --- a/frontend_multi_user/src/app.py +++ b/frontend_multi_user/src/app.py @@ -1038,6 +1038,7 @@ def inject_current_user_name(): @self.app.route('/') def index(): user = None + admin_account = None is_admin = False onboarding_steps: list[dict] = [] @@ -1054,7 +1055,7 @@ def index(): user_id = str(user.id) if user else None if user and user_id: - account_id = admin_account.id if is_admin and admin_account else (user.id if hasattr(user, 'id') else None) + account_id = getattr(admin_account, 'id', None) if is_admin else getattr(user, 'id', None) # Step 1: Account created (always done if logged in) onboarding_steps.append({