Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
00c48fc
Add post-subscription onboarding design spec.
paulocastellano Jul 24, 2026
5b8e6ff
Add post-subscription onboarding implementation plan.
paulocastellano Jul 24, 2026
44fb9be
Add account onboarding completed and dismissed timestamps.
paulocastellano Jul 24, 2026
a69b799
Add ResolveOnboardingStatus for activation checklist state.
paulocastellano Jul 24, 2026
abf247e
Fix ResolveOnboardingStatus null-account guard and workspace scoping …
paulocastellano Jul 24, 2026
7655c1f
Rename pre-subscription ICP flow to Welcome routes.
paulocastellano Jul 24, 2026
7786204
Fix Task 3 review: rename SocialController welcome test label.
paulocastellano Jul 24, 2026
9827714
Add Welcome Vue pages and i18n; drop social from subscribe.
paulocastellano Jul 24, 2026
1c13007
Add post-subscription onboarding activation endpoints.
paulocastellano Jul 24, 2026
ea615a6
Add single-page post-subscription onboarding UI.
paulocastellano Jul 24, 2026
4726204
Send post-checkout users to onboarding instead of accounts.
paulocastellano Jul 24, 2026
ec64768
Add dismissible residual onboarding banner in app shell.
paulocastellano Jul 24, 2026
ff462e5
Track welcome and onboarding activation events in PostHog.
paulocastellano Jul 24, 2026
254e7b4
Fix post-subscription onboarding review findings.
paulocastellano Jul 24, 2026
fb7a00b
Remove welcome subscribe step and start Stripe from referral.
paulocastellano Jul 24, 2026
5dd4983
Use AppLayout with sidebar on the onboarding activation page.
paulocastellano Jul 24, 2026
8659da8
Polish welcome and onboarding UX and unify the sidebar account menu.
paulocastellano Jul 24, 2026
a1254b4
Refactor billing configuration and subscription checkout process.
paulocastellano Jul 26, 2026
0d75b54
Polish onboarding realtime updates and tighten activation code.
paulocastellano Jul 26, 2026
efc902f
Localize onboarding and welcome strings across all locales.
paulocastellano Jul 26, 2026
996310c
Translate remaining welcome progress labels across locales.
paulocastellano Jul 26, 2026
54b57f2
Add workspace MCP settings page and polish sidebar nav.
paulocastellano Jul 26, 2026
a7f0865
Align MCP settings cards with TryPost neobrutalist UI.
paulocastellano Jul 26, 2026
64354fb
Make ChatGPT MCP icon use currentColor for dark tiles.
paulocastellano Jul 26, 2026
de12c16
Fix MCP accordion cards clipping their bottom border.
paulocastellano Jul 26, 2026
5260f4e
Match Claude and ChatGPT MCP connect cards to onboarding.
paulocastellano Jul 26, 2026
0f19048
Remove open-state border under MCP accordion titles.
paulocastellano Jul 26, 2026
5d39ff0
Give open MCP accordion content a bit more top spacing.
paulocastellano Jul 26, 2026
74d558d
Add MCP link to the workspace section of the sidebar.
paulocastellano Jul 26, 2026
c423fcf
Point MCP settings docs link to the AI introduction page.
paulocastellano Jul 26, 2026
f3df17b
Drop the orphaned step number in other MCP client cards.
paulocastellano Jul 26, 2026
b7d427f
Keep Settings inactive in the sidebar on the MCP page.
paulocastellano Jul 26, 2026
b93153f
Tighten MCP settings copy and match HeadingSmall hierarchy.
paulocastellano Jul 26, 2026
a626edc
Localize MCP settings copy across all supported languages.
paulocastellano Jul 26, 2026
75f5017
Use a white background for the Cursor MCP client tile.
paulocastellano Jul 26, 2026
147b3bf
Harden onboarding MCP detection and post-checkout redirects.
paulocastellano Jul 26, 2026
0af1bae
Align account-scoped MCP onboarding and name the MCP route.
paulocastellano Jul 26, 2026
8455441
Broadcast MCP onboarding account-wide and list team connections.
paulocastellano Jul 26, 2026
5bd5099
Attribute onboarding analytics to the acting user and sync only them.
paulocastellano Jul 26, 2026
57701d5
Harden post-subscription onboarding edge cases from review.
cursoragent Jul 29, 2026
176ee41
Close remaining onboarding review follow-ups.
cursoragent Jul 29, 2026
ca2d350
Harden onboarding against transaction side effects and access gaps.
cursoragent Jul 29, 2026
0a7a590
Align MCP/API workspace access with hasAppAccess.
cursoragent Jul 29, 2026
8a46a5e
Broadcast onboarding complete/dismiss to clear residual immediately.
cursoragent Jul 29, 2026
9db85d8
Clear residual and sync sibling Echo on every onboarding stamp.
cursoragent Jul 29, 2026
6d470bb
Harden onboarding dismiss terminality and avoid double navigation.
cursoragent Jul 29, 2026
90bc604
Redirect completed onboarding revisits and fix residual progress.
cursoragent Jul 29, 2026
b6c72f0
Keep celebration after OAuth stamps completion via session flash.
cursoragent Jul 29, 2026
c833032
Prevent poll/Echo from consuming onboarding celebration flash.
cursoragent Jul 29, 2026
739e198
Polish checkout trial typing and align onboarding account scope.
cursoragent Jul 29, 2026
d36b7c0
Remove dead tryMarkAccountComplete and cover incomplete_expired trial.
cursoragent Jul 29, 2026
2dbabca
Merge origin/main into post-subscription onboarding.
cursoragent Jul 29, 2026
c8e183c
Merge origin/main into post-subscription onboarding.
paulocastellano Aug 1, 2026
fe8a849
Eager-load subscriptions in onboarding dismiss backfill.
paulocastellano Aug 1, 2026
9fc46ca
Refactor MCP client handling in onboarding and settings pages
paulocastellano Aug 1, 2026
7c8aaff
Refactor onboarding logic in AppSidebar and related components
paulocastellano Aug 1, 2026
0208ce1
Keep onboarding schema migration schema-only.
cursoragent Aug 1, 2026
2e7cab9
Fix onboarding review findings: perf, races, UX and a11y.
paulocastellano Aug 1, 2026
82bda00
Scope TrackPostTest owner-fallback assertion to the post.created event.
paulocastellano Aug 1, 2026
70bcf85
Hold members without app access on a dedicated screen; add funnel bro…
paulocastellano Aug 1, 2026
5a6783b
Update browser-test rules to data-testid selectors and in-process ser…
paulocastellano Aug 1, 2026
5208a3c
Polish remaining review items: mobile residual, analytics timing, con…
paulocastellano Aug 1, 2026
1e049a1
Fix celebration session flag and polish access redirects.
cursoragent Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 deletions .cursor/rules/tests-dusk.mdc
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
---
description: Laravel Dusk browser tests — named routes, dusk selectors, no CSS/text-based assertions
description: Browser tests (Pest + Playwright) — named routes, data-testid selectors, no CSS/text-based assertions
globs: tests/Browser/**/*.php
alwaysApply: false
---

# Dusk Browser Tests
# Browser Tests

## Named routes (IMPORTANT)

ALWAYS use named routes via the `route()` helper. NEVER hardcode URLs like `'https://trypost.test/login'`.

```php
// BAD
$browser->visit('https://trypost.test/login');
visit('https://trypost.test/login');

// GOOD
$browser->visit(route('login'));
visit(route('login'));
```

## Dusk selectors
## Selectors (IMPORTANT)

ALWAYS use `dusk` selectors (`@selector-name`) for interactions and assertions. NEVER use CSS classes (`.text-red-600`), tag names, or text strings.
ALWAYS use `data-testid` attributes and target them with `@selector-name`. NEVER use CSS classes (`.text-red-600`), tag names, or text strings.

Add `dusk="my-element"` attributes to Vue components and target them with `$browser->click('@my-element')`, `$browser->waitFor('@my-element')`, etc.
The Pest browser plugin resolves `@selector` against `[data-testid]` / `[data-test]` only — `dusk="..."` attributes are NOT matched (legacy from Laravel Dusk).

Add `data-testid="my-element"` attributes to Vue components and target them with `$page->click('@my-element')`, `$page->assertVisible('@my-element')`, etc.

```php
// BAD
$browser->waitFor('.text-red-600');
$browser->click('button.primary');
$browser->assertSee('Welcome');
$page->assertVisible('.text-red-600');
$page->click('button.primary');

// GOOD
$browser->waitFor('@input-error');
$browser->click('@submit-button');
$browser->assertVisible('@welcome-message');
$page->assertVisible('@input-error');
$page->click('@submit-button');
```

```vue
<!-- Vue component -->
<button dusk="submit-button" @click="handleSubmit">Submit</button>
<span dusk="input-error" v-if="error">{{ error }}</span>
<button data-testid="submit-button" @click="handleSubmit">Submit</button>
<span data-testid="input-error" v-if="error">{{ error }}</span>
```

## No auto-wait

Browser assertions do not auto-wait. Poll browser-side (see `waitForTestId` / `waitForDusk` helpers in existing tests) before asserting on async UI.

## Strict models

Use `->fresh()` when acting as a factory-created user — the in-process server keeps the guard user across requests, and factory instances lack nullable columns (strict mode throws `MissingAttributeException`).
14 changes: 10 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,17 @@ STRIPE_SECRET=
STRIPE_WEBHOOK_SECRET=
# Set to false to allow generic signup trial without requiring a card.
REQUIRE_CARD_FOR_TRIAL=true
# Free trial length in days, used only for the no-card generic trial above.
# Checkout trial length in days (card collected up front when REQUIRE_CARD_FOR_TRIAL=true).
# - >0: Stripe Checkout trial for N days for first-time signups only (no prior
# real subscription). Values below 2 are clamped to 2 (Stripe's 48h min).
# - 0: no Checkout trial; charge starts immediately.
# - Returning / canceled accounts never get another Checkout trial.
# - When REQUIRE_CARD_FOR_TRIAL=false, Checkout never applies a trial (generic
# signup trial uses this value instead; must be >= 1 in that mode).
# Empty = default 8.
CASHIER_TRIAL_DAYS=8
# Stripe Coupon ID (amount_off, duration=once) so the first invoice at
# checkout comes out to $1 instead of the full monthly price.
STRIPE_FIRST_MONTH_COUPON_ID=
# Show Stripe Checkout promotion-code field.
CASHIER_ALLOW_PROMOTION_CODES=true

# Stripe Plan Price IDs (one per plan × interval). Used by PlanSeeder.
STRIPE_WORKSPACE_MONTHLY=
Expand Down
17 changes: 10 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,16 @@ Vue components must have a single root element.
- Example: `$this->postJson(route('app.posts.store'))` instead of `$this->postJson('/posts')`.
- With params: `route('app.posts.ai.create.finalize', $creationId)`.

## Dusk (Browser Tests)

- In Dusk tests, ALWAYS use named routes via `route()` helper. NEVER hardcode URLs like `'https://trypost.test/login'`.
- Example: `$browser->visit(route('login'))` instead of `$browser->visit('https://trypost.test/login')`.
- ALWAYS use `dusk` selectors (`@selector-name`) for interacting with and asserting elements. NEVER use CSS classes (`.text-red-600`), tag names, or text strings.
- Add `dusk="my-element"` attributes to Vue components and use `$browser->click('@my-element')`, `$browser->waitFor('@my-element')`, etc.
- Example: `$browser->waitFor('@input-error')` instead of `$browser->waitFor('.text-red-600')`.
## Browser Tests

- In browser tests, ALWAYS use named routes via `route()` helper. NEVER hardcode URLs like `'https://trypost.test/login'`.
- Example: `visit(route('login'))` instead of `visit('https://trypost.test/login')`.
- ALWAYS use `data-testid` selectors (`@selector-name`) for interacting with and asserting elements. NEVER use CSS classes (`.text-red-600`), tag names, or text strings.
- The Pest browser plugin resolves `@selector` against `[data-testid]` / `[data-test]` only — `dusk="..."` attributes are NOT matched.
- Add `data-testid="my-element"` attributes to Vue components and use `$page->click('@my-element')`, `$page->assertVisible('@my-element')`, etc.
- Example: `$page->assertVisible('@input-error')` instead of `$page->assertVisible('.text-red-600')`.
- Assertions do not auto-wait — poll browser-side (see `waitForTestId`/`waitForDusk` helpers in `tests/Browser`) before asserting on async UI.
- Use `->fresh()` when acting as a factory-created user: the in-process server keeps the guard user across requests, and factory instances lack nullable columns (strict mode throws `MissingAttributeException`).

## Array Data Access

Expand Down
9 changes: 4 additions & 5 deletions app/Actions/Billing/StartSubscriptionCheckout.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
namespace App\Actions\Billing;

use App\Models\Account;
use App\Support\Billing\FirstMonthCheckoutDiscount;
use App\Support\Billing\ConfigureSubscriptionCheckout;
use Inertia\Inertia;
use Symfony\Component\HttpFoundation\Response;

class StartSubscriptionCheckout
{
/**
* Create a Stripe Checkout session for the given price and return an Inertia
* redirect to it. Quantity tracks the account's workspace count; the first
* month's coupon is applied when the instance requires a card up front, so
* the first invoice charges $1 instead of running a $0 trial authorization.
* redirect to it. Quantity tracks the account's workspace count. Trial days
* and promotion codes come from Cashier env config.
*/
public function redirect(Account $account, string $priceId, string $cancelUrl): Response
{
Expand All @@ -27,7 +26,7 @@ public function redirect(Account $account, string $priceId, string $cancelUrl):
$subscription = $account->newSubscription(Account::SUBSCRIPTION_NAME, $priceId)
->quantity(max(1, $account->workspaces()->count()));

FirstMonthCheckoutDiscount::apply($subscription, $account);
ConfigureSubscriptionCheckout::apply($subscription, $account);

$session = $subscription->checkout([
'success_url' => route('app.billing.processing').'?session_id={CHECKOUT_SESSION_ID}',
Expand Down
Loading