Skip to content

Commit dee9e37

Browse files
trial: drop trial_ends_at + 'trial' subscription_status from API types and UI copy (W10) (#56)
Mirrors the api W10 trial removal. Drops trial_ends_at from /auth/me response type and 5 mock fixtures; removes 'trial' from the subscription_status union; deletes the 'Trial vs. immediate Hobby' lock banner from ContractsPage; rewrites the 'anonymous tier is the trial' copy on UseCaseDetailPage; trims the trailing + trial_ends_at from ClaimPage. Per policy memory project_no_trial_pay_day_one.md. 417 vitest passes, tsc clean. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 67df0f0 commit dee9e37

5 files changed

Lines changed: 18 additions & 19 deletions

File tree

src/api/index.test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,6 @@ describe('fetchMe()', () => {
604604
team_id: 't_xyz',
605605
email: 'agent@instanode.dev',
606606
tier: 'pro',
607-
trial_ends_at: null,
608607
}))
609608
const r = await fetchMe()
610609
expect(r.user.id).toBe('u_xyz')
@@ -651,7 +650,6 @@ describe('fetchMe()', () => {
651650
team_id: 't_xyz',
652651
email: 'agent@instanode.dev',
653652
tier: 'pro',
654-
trial_ends_at: null,
655653
experiments: { upgrade_button: 'urgent' },
656654
}))
657655
const r = await fetchMe()
@@ -669,7 +667,6 @@ describe('fetchMe()', () => {
669667
team_id: 't_xyz',
670668
email: 'agent@instanode.dev',
671669
tier: 'pro',
672-
trial_ends_at: null,
673670
}))
674671
const r = await fetchMe()
675672
expect(r.experiments).toBeUndefined()
@@ -1296,7 +1293,6 @@ describe('Admin URL prefix wiring', () => {
12961293
team_id: 't_admin',
12971294
email: 'founder@instanode.dev',
12981295
tier: 'team',
1299-
trial_ends_at: null,
13001296
is_platform_admin: true,
13011297
admin_path_prefix: 'abcdefghijklmnopqrstuvwxyz012345',
13021298
}))
@@ -1315,7 +1311,6 @@ describe('Admin URL prefix wiring', () => {
13151311
team_id: 't_user',
13161312
email: 'alice@example.com',
13171313
tier: 'hobby',
1318-
trial_ends_at: null,
13191314
// is_platform_admin and admin_path_prefix both absent
13201315
}))
13211316
const { fetchMe, getAdminPathPrefix, setAdminPathPrefix } = await import('./index')

src/api/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,20 @@ async function call<T>(
160160

161161
// ─── Auth / me ───────────────────────────────────────────────────────────
162162
// GET /auth/me on the agent API returns:
163-
// { ok, user_id, team_id, email, tier, trial_ends_at, experiments }
163+
// { ok, user_id, team_id, email, tier, experiments }
164164
// The dashboard expected { user, team } — we adapt here so the rest of
165165
// the dashboard still consumes the richer fixture shape.
166+
//
167+
// Historical note: this response used to include a `trial_ends_at` field;
168+
// removed on 2026-05-14 per policy memory project_no_trial_pay_day_one.md.
169+
// The platform has no trial period — hobby/pro/team are paid from day one.
166170
export async function fetchMe(): Promise<AuthMeResponse> {
167171
type AgentMe = {
168172
ok: boolean
169173
user_id: string
170174
team_id: string
171175
email: string
172176
tier: string
173-
trial_ends_at: string | null
174177
/** A/B-test bucket per registered experiment, e.g.
175178
* `{ upgrade_button: "urgent" }`. Older API builds omit this
176179
* field entirely — callers must treat undefined as "no
@@ -1101,7 +1104,7 @@ export async function deleteCustomDomain(stackSlug: string, id: string): Promise
11011104
type BillingStateResp = {
11021105
ok: boolean
11031106
tier: string
1104-
subscription_status?: 'none' | 'active' | 'cancelled' | 'trial'
1107+
subscription_status?: 'none' | 'active' | 'cancelled'
11051108
next_renewal_at?: string | null
11061109
amount_inr?: number | null
11071110
payment_method?: {

src/pages/ClaimPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ export function ClaimPage() {
473473
</button>
474474

475475
<p style={{ marginTop: 18, fontSize: 11, color: 'var(--text-faint)', fontFamily: 'var(--font-mono)', lineHeight: 1.5 }}>
476-
POST /claim · atomic · single-use · returns session_token + trial_ends_at
476+
POST /claim · atomic · single-use · returns session_token
477477
</p>
478478
</div>
479479
</div>

src/pages/ContractsPage.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,22 +154,22 @@ data: {}`}</>}
154154
/>
155155
</div>
156156

157-
<SectionH label="NEEDS LOCK" badgeBg="var(--amber)" title="5 contradictions or partial implementations" sub="FE/BE will diverge until these resolve" />
157+
<SectionH label="NEEDS LOCK" badgeBg="var(--amber)" title="4 contradictions or partial implementations" sub="FE/BE will diverge until these resolve" />
158158

159159
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
160+
{/* The "Trial vs. immediate Hobby" banner was removed on 2026-05-14
161+
per policy memory project_no_trial_pay_day_one.md — the platform
162+
has no trial period; hobby/pro/team are paid from day one. */}
160163
<ContractBanner kind="warning" badge="#1">
161-
<strong>Trial vs. immediate Hobby.</strong> <code>plans.yaml</code> declares <code>trial_days: 14</code>; <code>auth.go:151</code> assigns <code>hobby</code> with no trial fields. Brief journey 1 assumes a trial. <strong>Lock:</strong> add <code>teams.trial_ends_at</code> + worker, OR drop trial language from copy.
162-
</ContractBanner>
163-
<ContractBanner kind="warning" badge="#2">
164164
<strong>"Deployments" vs "Stacks".</strong> Brief uses "Deployments"; the API uses "Stacks". <strong>Lock:</strong> dashboard URL is <code>/deployments</code> (user language), API stays <code>/stacks</code> (existing).
165165
</ContractBanner>
166-
<ContractBanner kind="warning" badge="#3">
166+
<ContractBanner kind="warning" badge="#2">
167167
<strong>Multi-env scoping.</strong> Resource shape includes <code>env</code> but list endpoint has no <code>?env=</code> filter. <strong>Lock:</strong> add server-side filter param + <code>teams.default_env</code> in PATCH body.
168168
</ContractBanner>
169-
<ContractBanner kind="warning" badge="#4">
169+
<ContractBanner kind="warning" badge="#3">
170170
<strong>Role changes.</strong> Members are invited with a role; there's no <code>PATCH /members/:id</code> for promotion/demotion. <strong>Lock:</strong> add <code>PATCH /api/v1/team/members/:user_id</code> with body <code>{`{role}`}</code> + audit row.
171171
</ContractBanner>
172-
<ContractBanner kind="warning" badge="#5">
172+
<ContractBanner kind="warning" badge="#4">
173173
<strong>Multi-service stacks.</strong> Brief separates <em>Deployments</em> (single service) from <em>Stacks</em> (multi-service compose). <code>DashboardStack</code> has no <code>services[]</code> field.
174174
</ContractBanner>
175175
</div>
@@ -200,7 +200,7 @@ data: {}`}</>}
200200
If we lock these in sequence, FE and BE can ship without merge-day surprises.
201201
</p>
202202
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
203-
<Week n={1} title="Lock the contradictions" body="Trial decision · /deployments mapping · multi-env filter param." />
203+
<Week n={1} title="Lock the contradictions" body="/deployments mapping · multi-env filter param." />
204204
<Week n={2} title="Vault contract" body="5 endpoints · schema · sudo flow. Largest single chunk." />
205205
<Week n={3} title="Logs SSE + actions" body="Redeploy / rollback / stop · SSE event format. Build view depends." />
206206
<Week n={4} title="Metrics + audit" body="Last to lock — design works without them on day one." />

src/pages/UseCaseDetailPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,9 @@ function AutoDetail({ services }: { services: Service[] }) {
167167
The first call returns a real resource in under a second.
168168
</li>
169169
<li>
170-
<strong>Anonymous-first.</strong> The 24-hour anonymous tier is the trial — every
171-
resource expires unless you claim it. No credit card needed to try.
170+
<strong>Anonymous-first.</strong> The 24-hour anonymous tier is your sandbox to
171+
evaluate the platform — every resource expires unless you claim it. No credit card
172+
needed to try.
172173
</li>
173174
<li>
174175
<strong>Real infrastructure, not a sandbox.</strong> Every Postgres is a real

0 commit comments

Comments
 (0)