From 12d893443544e7c1874672e081cb10ba1508573b Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sun, 4 Oct 2026 00:03:23 +0200 Subject: [PATCH 1/2] fix(web): send the Railway token as a RailwayConnectRequest instance The v1 integrations payloads are Schema.Class, so the client rejects a plain `{ token }` object while encoding and never sends the PUT. Every connect attempt showed "Failed to connect Railway" with no request reaching the API. Construct the class, as the other connect flows do. --- .../src/components/integrations/railway-integration-card.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/integrations/railway-integration-card.tsx b/apps/web/src/components/integrations/railway-integration-card.tsx index fb3b1d631..b0a570574 100644 --- a/apps/web/src/components/integrations/railway-integration-card.tsx +++ b/apps/web/src/components/integrations/railway-integration-card.tsx @@ -1,6 +1,7 @@ import { useState } from "react" import { Exit, Option } from "effect" import { Link } from "@tanstack/react-router" +import { RailwayConnectRequest } from "@maple/domain/http" import { Badge } from "@maple/ui/components/ui/badge" import { Button } from "@maple/ui/components/ui/button" import { Input } from "@maple/ui/components/ui/input" @@ -52,7 +53,8 @@ function RailwayTokenForm({ setSubmitting(true) setError(null) const result = await connect({ - payload: { token: token.trim() }, + // v1 payloads are Schema.Class: a plain object fails client-side encoding and is never sent. + payload: new RailwayConnectRequest({ token: token.trim() }), reactivityKeys: ["railwayIntegrationStatus"], }) setSubmitting(false) From 26ac2b4c0cee8fc9bca6192377055f41851c61b1 Mon Sep 17 00:00:00 2001 From: Makisuo Date: Sun, 4 Oct 2026 00:09:58 +0200 Subject: [PATCH 2/2] fix(web): validate the Railway token by decoding instead of the throwing constructor --- .../integrations/railway-integration-card.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/integrations/railway-integration-card.tsx b/apps/web/src/components/integrations/railway-integration-card.tsx index b0a570574..ce33b272e 100644 --- a/apps/web/src/components/integrations/railway-integration-card.tsx +++ b/apps/web/src/components/integrations/railway-integration-card.tsx @@ -1,5 +1,5 @@ import { useState } from "react" -import { Exit, Option } from "effect" +import { Exit, Option, Schema } from "effect" import { Link } from "@tanstack/react-router" import { RailwayConnectRequest } from "@maple/domain/http" import { Badge } from "@maple/ui/components/ui/badge" @@ -28,6 +28,8 @@ import { const TOKENS_URL = "https://railway.com/account/tokens" +const decodeConnectRequest = Schema.decodeUnknownOption(RailwayConnectRequest) + export const railwayStatusAtom = retainedQuery("integrations", "railwayStatus", { reactivityKeys: ["railwayIntegrationStatus"], }) @@ -48,13 +50,18 @@ function RailwayTokenForm({ const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) + // Decoding builds the class instance v1 payloads need (a plain object is never sent) and + // checks the length rules without the constructor's throw. + const request = decodeConnectRequest({ token: token.trim() }) + const tokenInvalid = token.trim().length > 0 && Option.isNone(request) + async function handleSubmit(event: React.FormEvent) { event.preventDefault() + if (Option.isNone(request)) return setSubmitting(true) setError(null) const result = await connect({ - // v1 payloads are Schema.Class: a plain object fails client-side encoding and is never sent. - payload: new RailwayConnectRequest({ token: token.trim() }), + payload: request.value, reactivityKeys: ["railwayIntegrationStatus"], }) setSubmitting(false) @@ -89,7 +96,7 @@ function RailwayTokenForm({ Cancel ) : null} -