Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1bac5f0
fix(chat): preserve final answers after tool calls
1052326311 Sep 30, 2026
a5b97d1
feat(i18n): add opt-in Simplified Chinese interface and runtime labels
1052326311 Sep 30, 2026
738865f
fix(chat): preserve reasoning markers until channel classification
1052326311 Sep 30, 2026
0379f7f
fix(chat): reconcile final SDK messages per message instead of per se…
1052326311 Sep 30, 2026
23a6c35
fix(chat): enable marker-preserving event mapping
1052326311 Sep 30, 2026
e53a3a1
fix(i18n): preserve upstream onboarding and metadata
1052326311 Sep 30, 2026
b777252
style(i18n): format localized UI and catalogs for review
1052326311 Sep 30, 2026
0aac1eb
fix(i18n): narrow matched status captures under strict indexing
1052326311 Sep 30, 2026
2aea4f6
fix(i18n): make server and client formatting timezone deterministic
1052326311 Sep 30, 2026
9064537
style(chat): format final-response regression tests
1052326311 Sep 30, 2026
dc3c556
fix: count raw SDK text before sanitizing catch-up offsets
1052326311 Sep 30, 2026
87b6c07
style: format catch-up offset regression cases
1052326311 Sep 30, 2026
516092b
fix: persist plan progress and separate it from execution lifecycle
1052326311 Sep 30, 2026
5f2b241
merge: integrate plan lifecycle fix for localized status labels
1052326311 Sep 30, 2026
62a4aee
feat(i18n): persist account language and move switcher into platform …
1052326311 Sep 30, 2026
c3bffa8
Merge branch 'fix/final-answer-after-tools' into fix/plan-progress-li…
1052326311 Oct 1, 2026
6619d7b
fix: reconcile execution using structured task and tool outcomes
1052326311 Oct 1, 2026
eaa9fb0
merge: share lifecycle and final-response fixes with localized editor
1052326311 Oct 1, 2026
31d688f
fix: reconcile SDK tool events and cached stream callbacks
1052326311 Oct 1, 2026
fe6ae15
fix(i18n): decouple UI behavior from translated labels
1052326311 Oct 1, 2026
3cfe9da
Merge branch 'fix/plan-progress-lifecycle' into feat/english-chinese-…
1052326311 Oct 1, 2026
2bd8866
fix: ignore dispatch acknowledgments and deduplicate SDK mirrors
1052326311 Oct 1, 2026
f0a23b7
Merge branch 'fix/plan-progress-lifecycle' into feat/english-chinese-…
1052326311 Oct 1, 2026
ae6259e
fix: decode SDK result envelopes without parsing project file content
1052326311 Oct 1, 2026
84453c9
Merge branch 'fix/plan-progress-lifecycle' into feat/english-chinese-…
1052326311 Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3,905 changes: 3,905 additions & 0 deletions apps/web/messages/en.json

Large diffs are not rendered by default.

3,903 changes: 3,903 additions & 0 deletions apps/web/messages/index.json

Large diffs are not rendered by default.

39,435 changes: 39,435 additions & 0 deletions apps/web/messages/source.json

Large diffs are not rendered by default.

3,905 changes: 3,905 additions & 0 deletions apps/web/messages/zh-CN.json

Large diffs are not rendered by default.

44 changes: 32 additions & 12 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";

const nextConfig: NextConfig = {
output: "standalone",
Expand All @@ -17,7 +18,11 @@ const nextConfig: NextConfig = {
allowedDevOrigins: (() => {
const fromUrl = (u: string | undefined) => {
if (!u) return null;
try { return new URL(u).hostname; } catch { return null; }
try {
return new URL(u).hostname;
} catch {
return null;
}
};
const apex = (host: string) => {
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) return host;
Expand All @@ -26,10 +31,16 @@ const nextConfig: NextConfig = {
};
// Dev-only cross-origin allowlist. Derive from the install env; never
// hardcode our own domain so self-hosters don't carry `*.doable.me`.
const host = fromUrl(process.env.NEXT_PUBLIC_APP_URL)
|| fromUrl(process.env.NEXT_PUBLIC_API_URL)
|| "";
return [host, host ? `*.${apex(host)}` : "", "localhost", "127.0.0.1"].filter(Boolean);
const host =
fromUrl(process.env.NEXT_PUBLIC_APP_URL) ||
fromUrl(process.env.NEXT_PUBLIC_API_URL) ||
"";
return [
host,
host ? `*.${apex(host)}` : "",
"localhost",
"127.0.0.1",
].filter(Boolean);
})(),
experimental: {
serverActions: {
Expand All @@ -54,17 +65,21 @@ const nextConfig: NextConfig = {
},
async redirects() {
return [
{ source: '/settings/ai', destination: '/ai-settings', permanent: false },
{ source: '/settings/usage', destination: '/usage', permanent: false },
{ source: '/settings/billing', destination: '/billing', permanent: false },
{ source: "/settings/ai", destination: "/ai-settings", permanent: false },
{ source: "/settings/usage", destination: "/usage", permanent: false },
{
source: "/settings/billing",
destination: "/billing",
permanent: false,
},
];
},
async rewrites() {
return [
// Bare /favicon.ico requests (crawlers, browsers without <link rel="icon">)
// are served by the dynamic icon.tsx route. Standalone build doesn't auto-alias
// icon.tsx to /favicon.ico, so we wire it explicitly.
{ source: '/favicon.ico', destination: '/icon' },
{ source: "/favicon.ico", destination: "/icon" },
];
},
async headers() {
Expand Down Expand Up @@ -111,7 +126,13 @@ const nextConfig: NextConfig = {
// allow="microphone; autoplay".
const apiUrlRaw = process.env.NEXT_PUBLIC_API_URL || "";
const previewOrigin = apiUrlRaw
? (() => { try { return new URL(apiUrlRaw).origin; } catch { return ""; } })()
? (() => {
try {
return new URL(apiUrlRaw).origin;
} catch {
return "";
}
})()
: "";
const micList = previewOrigin ? `(self "${previewOrigin}")` : "(self)";
// BUG-016: CSP was applying `unsafe-eval` + `unsafe-inline` to every
Expand Down Expand Up @@ -206,5 +227,4 @@ const nextConfig: NextConfig = {
},
};

export default nextConfig;

export default createNextIntlPlugin("./src/i18n/request.ts")(nextConfig);
3 changes: 2 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"lucide-react": "^0.468.0",
"monaco-editor": "^0.55.1",
"next": "^16.2.0",
"next-intl": "4.14.8",
"qrcode": "^1.5.4",
"react": "^19.2.0",
"react-dom": "^19.2.0",
Expand All @@ -48,8 +49,8 @@
"@opennextjs/cloudflare": "^1.17.1",
"@tailwindcss/postcss": "^4.0.0",
"@types/node": "^22.10.5",
"@types/react": "^19.2.0",
"@types/qrcode": "^1.5.5",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@typescript-eslint/eslint-plugin": "^8.20.0",
"@typescript-eslint/parser": "^8.20.0",
Expand Down
17 changes: 13 additions & 4 deletions apps/web/src/app/(auth)/error.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client";
import { useUiText } from "@/i18n/use-ui-text";

import { useEffect } from "react";
import Link from "next/link";
Expand All @@ -10,26 +11,34 @@ export default function AuthError({
error: Error & { digest?: string };
reset: () => void;
}) {
const ui = useUiText();

useEffect(() => {
console.error("Auth error:", error);
}, [error]);

return (
<div className="flex min-h-screen flex-col items-center justify-center bg-[#0a0a0a] px-4">
<h2 className="text-xl font-semibold text-white mb-2">Authentication Error</h2>
<p className="text-sm text-zinc-400 mb-6">{error.message || "Something went wrong."}</p>
<h2 className="text-xl font-semibold text-white mb-2">
{ui("Authentication Error")}
</h2>
<p className="text-sm text-zinc-400 mb-6">
{ui(error.message) || ui("Something went wrong.")}
</p>
<div className="flex gap-3">
<button
onClick={reset}
className="rounded-lg bg-zinc-800 px-4 py-2 text-sm text-zinc-300 hover:bg-zinc-700 transition-colors"
>
Try again
{" "}
{ui("Try again")}{" "}
</button>
<Link
href="/login"
className="rounded-lg bg-brand-600 px-4 py-2 text-sm text-white hover:bg-brand-500 transition-colors"
>
Back to Login
{" "}
{ui("Back to Login")}{" "}
</Link>
</div>
</div>
Expand Down
52 changes: 32 additions & 20 deletions apps/web/src/app/(auth)/forgot-password/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client";
import { useUiText } from "@/i18n/use-ui-text";

import { useState, type FormEvent } from "react";
import Link from "next/link";
Expand All @@ -10,6 +11,8 @@ import { ArrowLeft, Loader2, Mail } from "lucide-react";
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";

export default function ForgotPasswordPage() {
const ui = useUiText();

const [email, setEmail] = useState("");
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
Expand All @@ -28,16 +31,18 @@ export default function ForgotPasswordPage() {
});

if (!res.ok) {
const body = await res.json().catch(() => ({ error: "Request failed" }));
throw new Error(body.error ?? "Request failed");
const body = await res
.json()
.catch(() => ({ error: "Request failed" }));
throw new Error(body.error ?? ui("Request failed"));
}

setIsSubmitted(true);
} catch (err: unknown) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("Something went wrong. Please try again.");
setError(ui("Something went wrong. Please try again."));
}
} finally {
setIsLoading(false);
Expand All @@ -51,14 +56,18 @@ export default function ForgotPasswordPage() {
<Mail className="h-6 w-6 text-green-600 dark:text-green-400" />
</div>
<h2 className="mb-2 text-xl font-semibold text-[hsl(var(--foreground))]">
Check your email
{" "}
{ui("Check your email")}{" "}
</h2>
<p className="mb-6 text-sm text-[hsl(var(--muted-foreground))]">
If an account exists for <strong className="text-[hsl(var(--foreground))]">{email}</strong>,
we&apos;ve sent password reset instructions to your inbox.
{" "}
{ui("If an account exists for")}{" "}
<strong className="text-[hsl(var(--foreground))]">{email}</strong>
{ui(", we've sent password reset instructions to your inbox.")}{" "}
</p>
<p className="mb-6 text-xs text-[hsl(var(--muted-foreground))]">
Didn&apos;t receive the email? Check your spam folder or{" "}
{" "}
{ui("Didn't receive the email? Check your spam folder or")}{" "}
<button
type="button"
onClick={() => {
Expand All @@ -67,15 +76,15 @@ export default function ForgotPasswordPage() {
}}
className="font-medium text-brand-700 hover:underline"
>
try again
{" "}
{ui("try again")}{" "}
</button>
</p>
<Link
href="/login"
className="inline-flex items-center gap-1.5 text-sm font-medium text-brand-700 hover:underline"
>
<ArrowLeft className="h-4 w-4" />
Back to sign in
<ArrowLeft className="h-4 w-4" /> {ui("Back to sign in")}{" "}
</Link>
</div>
);
Expand All @@ -88,10 +97,14 @@ export default function ForgotPasswordPage() {
<Mail className="h-6 w-6 text-brand-700" />
</div>
<h2 className="text-xl font-semibold text-[hsl(var(--foreground))]">
Forgot your password?
{" "}
{ui("Forgot your password?")}{" "}
</h2>
<p className="mt-2 text-sm text-[hsl(var(--muted-foreground))]">
No worries. Enter your email and we&apos;ll send you reset instructions.
{" "}
{ui(
"No worries. Enter your email and we'll send you reset instructions.",
)}{" "}
</p>
</div>

Expand All @@ -105,16 +118,16 @@ export default function ForgotPasswordPage() {
>
<path d="M8 1a7 7 0 100 14A7 7 0 008 1zm-.75 4.75a.75.75 0 011.5 0v3a.75.75 0 01-1.5 0v-3zM8 11a1 1 0 110 2 1 1 0 010-2z" />
</svg>
<span>{error}</span>
<span>{ui(error)}</span>
</div>
)}

<div className="space-y-2">
<Label htmlFor="email">Email address</Label>
<Label htmlFor="email">{ui("Email address")}</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
placeholder={ui("you@example.com")}
autoComplete="email"
required
disabled={isLoading}
Expand All @@ -131,11 +144,11 @@ export default function ForgotPasswordPage() {
>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Sending...
<Loader2 className="mr-2 h-4 w-4 animate-spin" />{" "}
{ui("Sending...")}{" "}
</>
) : (
"Send reset instructions"
ui("Send reset instructions")
)}
</Button>
</form>
Expand All @@ -145,8 +158,7 @@ export default function ForgotPasswordPage() {
href="/login"
className="inline-flex items-center gap-1.5 text-sm font-medium text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] transition-colors"
>
<ArrowLeft className="h-4 w-4" />
Back to sign in
<ArrowLeft className="h-4 w-4" /> {ui("Back to sign in")}{" "}
</Link>
</p>
</>
Expand Down
Loading