Skip to content

Commit 5578113

Browse files
authored
Merge branch 'main' into posthog-proxy-app-analytics
2 parents aa0771e + fbd86b6 commit 5578113

21 files changed

Lines changed: 1465 additions & 237 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Add an `onEvent` callback to `TriggerChatTransport` / `useTriggerChatTransport` that emits typed lifecycle events for sends, stream connects, first chunk, and turn completion. Send-success metrics, time-to-first-token, and "sent but never answered" watchdogs become a few lines of client code.
6+
7+
```ts
8+
onEvent: (event) => {
9+
if (event.type === "message-sent") metrics.timing("chat.send_ms", event.durationMs);
10+
if (event.type === "first-chunk") metrics.timing("chat.ttft_ms", event.sinceSendMs ?? 0);
11+
},
12+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
Large batch payloads now offload to object storage instead of riding inline in the trigger request. `batchTrigger` and `batchTriggerAndWait` (and the by-id and by-task variants) offload any per-item payload over 128KB before sending, the same way single `trigger` and `triggerAndWait` already do, so a big batch no longer blows past the API body limit.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
AI generation cost now uses the exact provider-reported cost from OpenRouter/Vercel AI Gateway when present, instead of catalog pricing, so cache-discounted and fallback-routed requests match the amount the provider actually billed.

.server-changes/login-sso-ui.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
A cleaner sign-in flow: request a magic link right from the login page, or continue with SSO.

apps/webapp/app/components/LoginPageLayout.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ import { MiddayLogo } from "~/assets/logos/MiddayLogo";
66
import { TldrawLogo } from "~/assets/logos/TldrawLogo";
77
import { UnkeyLogo } from "~/assets/logos/UnkeyLogo";
88
import { LogoType } from "./LogoType";
9-
import { LinkButton } from "./primitives/Buttons";
109
import { Header3 } from "./primitives/Headers";
1110
import { Paragraph } from "./primitives/Paragraph";
1211
import { TextLink } from "./primitives/TextLink";
13-
import { BookOpenIcon } from "@heroicons/react/20/solid";
1412

1513
interface QuoteType {
1614
quote: string;
@@ -53,13 +51,6 @@ export function LoginPageLayout({ children }: { children: React.ReactNode }) {
5351
<a href="https://trigger.dev">
5452
<LogoType className="w-36" />
5553
</a>
56-
<LinkButton
57-
to="https://trigger.dev/docs"
58-
variant={"tertiary/small"}
59-
LeadingIcon={BookOpenIcon}
60-
>
61-
Documentation
62-
</LinkButton>
6354
</div>
6455
<div className="flex h-full max-w-sm items-center justify-center">{children}</div>
6556
<Paragraph variant="small" className="text-center">

apps/webapp/app/routes/login._index/route.tsx

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1+
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
2+
import { parseWithZod } from "@conform-to/zod";
13
import { EnvelopeIcon, LockClosedIcon } from "@heroicons/react/20/solid";
24
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
3-
import { Form } from "@remix-run/react";
5+
import { Form, useNavigation } from "@remix-run/react";
46
import { GitHubLightIcon } from "@trigger.dev/companyicons";
57
import { motion, useReducedMotion } from "framer-motion";
68
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
9+
import { z } from "zod";
710
import { GoogleLogo } from "~/assets/logos/GoogleLogo";
811
import { LoginPageLayout } from "~/components/LoginPageLayout";
912
import { Button, LinkButton } from "~/components/primitives/Buttons";
1013
import { Callout } from "~/components/primitives/Callout";
1114
import { Fieldset } from "~/components/primitives/Fieldset";
1215
import { FormError } from "~/components/primitives/FormError";
1316
import { Header1 } from "~/components/primitives/Headers";
17+
import { Input } from "~/components/primitives/Input";
18+
import { InputGroup } from "~/components/primitives/InputGroup";
1419
import { Paragraph } from "~/components/primitives/Paragraph";
20+
import { Spinner } from "~/components/primitives/Spinner";
1521
import { TextLink } from "~/components/primitives/TextLink";
1622
import { isGithubAuthSupported, isGoogleAuthSupported } from "~/services/auth.server";
1723
import { getLastAuthMethod } from "~/services/lastAuthMethod.server";
@@ -24,6 +30,16 @@ import { requestUrl } from "~/utils/requestUrl.server";
2430
import { SSO_SESSION_EXPIRED_REASON } from "~/utils/ssoSession";
2531
import { cn } from "~/utils/cn";
2632

33+
// Client-side email validation for the inline magic-link form. Mirrors
34+
// /login/sso: the form posts cross-route to /login/magic, so conform runs
35+
// format validation in the browser and renders the styled inline error.
36+
// Server-side errors still surface via the session-backed authError below.
37+
const emailSchema = z.object({
38+
email: z
39+
.string({ required_error: "Enter your email address" })
40+
.email("Enter a valid email address"),
41+
});
42+
2743
function LastUsedBadge({ className }: { className?: string }) {
2844
const shouldReduceMotion = useReducedMotion();
2945

@@ -143,6 +159,22 @@ export async function loader({ request }: LoaderFunctionArgs) {
143159

144160
export default function LoginPage() {
145161
const data = useTypedLoaderData<typeof loader>();
162+
const navigation = useNavigation();
163+
// The inline email form posts to the /login/magic action, so reflect its
164+
// in-flight state here to show the sending spinner.
165+
const isEmailLoading =
166+
(navigation.state === "submitting" || navigation.state === "loading") &&
167+
navigation.formAction === "/login/magic" &&
168+
navigation.formData?.get("action") === "send";
169+
170+
const [emailForm, emailFields] = useForm({
171+
id: "login-email",
172+
onValidate({ formData }) {
173+
return parseWithZod(formData, { schema: emailSchema });
174+
},
175+
shouldValidate: "onBlur",
176+
shouldRevalidate: "onInput",
177+
});
146178

147179
return (
148180
<LoginPageLayout>
@@ -200,44 +232,80 @@ export default function LoginPage() {
200232
</Form>
201233
</div>
202234
)}
203-
{!data.isVercelMarketplace && (
235+
{data.showSsoAuth && !data.isVercelMarketplace && (
204236
<div className="relative w-full">
205-
{data.lastAuthMethod === "email" && <LastUsedBadge />}
237+
{data.lastAuthMethod === "sso" && <LastUsedBadge />}
206238
<LinkButton
207239
to={
208240
data.redirectTo
209-
? `/login/magic?redirectTo=${encodeURIComponent(data.redirectTo)}`
210-
: "/login/magic"
241+
? `/login/sso?redirectTo=${encodeURIComponent(data.redirectTo)}`
242+
: "/login/sso"
211243
}
212244
variant="secondary/extra-large"
213245
fullWidth
214-
data-action="continue with email"
246+
data-action="continue with sso"
215247
className="text-text-bright"
216248
>
217-
<EnvelopeIcon className="mr-2 size-5 text-text-bright" />
218-
Continue with Email
249+
<LockClosedIcon className="mr-2 size-5 text-text-bright" />
250+
Continue with SSO
219251
</LinkButton>
220252
</div>
221253
)}
222-
{data.showSsoAuth && !data.isVercelMarketplace && (
223-
<div className="flex w-full flex-col items-center gap-y-2 pt-2">
224-
<div className="h-px w-full bg-charcoal-700" />
225-
<div className="relative inline-flex items-center">
226-
{data.lastAuthMethod === "sso" && <LastUsedBadge className="translate-x-2" />}
227-
<TextLink
228-
to={
229-
data.redirectTo
230-
? `/login/sso?redirectTo=${encodeURIComponent(data.redirectTo)}`
231-
: "/login/sso"
232-
}
233-
className="inline-flex items-center text-sm"
234-
data-action="continue with sso"
254+
{!data.isVercelMarketplace && (
255+
<>
256+
{(data.showGithubAuth || data.showGoogleAuth || data.showSsoAuth) && (
257+
<div className="flex w-full items-center gap-3 py-1">
258+
<div className="h-px flex-1 bg-charcoal-700" />
259+
<span className="text-xs uppercase text-text-dimmed">or</span>
260+
<div className="h-px flex-1 bg-charcoal-700" />
261+
</div>
262+
)}
263+
<div className="w-full">
264+
{/* Posts to the /login/magic action so all magic-link logic
265+
(rate limiting, SSO auto-discovery, send) stays in one place. */}
266+
<Form
267+
action="/login/magic"
268+
method="post"
269+
className="w-full"
270+
{...getFormProps(emailForm)}
235271
>
236-
<LockClosedIcon className="mr-1.5 size-4" />
237-
Sign in with SSO
238-
</TextLink>
272+
<input type="hidden" name="action" value="send" />
273+
<div className="flex w-full flex-col items-center gap-y-2">
274+
<InputGroup fullWidth>
275+
<Input
276+
{...getInputProps(emailFields.email, { type: "email" })}
277+
spellCheck={false}
278+
placeholder="Email Address"
279+
variant="large"
280+
data-action="email address"
281+
/>
282+
<FormError id={emailFields.email.errorId}>
283+
{emailFields.email.errors}
284+
</FormError>
285+
</InputGroup>
286+
<div className="relative w-full">
287+
{data.lastAuthMethod === "email" && <LastUsedBadge />}
288+
<Button
289+
type="submit"
290+
variant="primary/large"
291+
disabled={isEmailLoading}
292+
fullWidth
293+
data-action="continue with email"
294+
>
295+
{isEmailLoading ? (
296+
<Spinner className="mr-2 size-5" color="white" />
297+
) : (
298+
<EnvelopeIcon className="mr-2 size-5 text-text-bright" />
299+
)}
300+
<span className="text-text-bright">
301+
{isEmailLoading ? "Sending…" : "Continue with Email"}
302+
</span>
303+
</Button>
304+
</div>
305+
</div>
306+
</Form>
239307
</div>
240-
</div>
308+
</>
241309
)}
242310
{data.authError && <FormError>{data.authError}</FormError>}
243311
</div>

0 commit comments

Comments
 (0)