Files
kandev/apps/web/app/layout.tsx
T
João Salavisa 569a0d77bd fix(office): unbreak new-task dialog "Create Task" button (#976)
* fix(office): unbreak new-task dialog "Create Task" button

Office task creation silently failed: the button click sent a malformed
payload, the backend dropped it, and no toast surfaced because Sonner's
Toaster was never mounted. Three causes, all fixed here.

- web: send priority as a string id ("medium") instead of a number; the
  backend Priority field is a string and ShouldBindJSON was rejecting
  the body with "invalid payload".
- web/backend: add project_id as a top-level field on createTask. The
  dialog was burying it inside metadata where the HTTP handler never
  read it, so isOfficeRequest() returned false and validation tripped
  on "workflow_id is required for non-ephemeral tasks".
- web: mount the Sonner Toaster in the root layout. All sonner-based
  toasts across the app (not just this dialog) were silently dropped
  because no Toaster was rendered anywhere.

* fix(office): address PR #976 review feedback

- new-task-dialog: drop unreachable `|| undefined` guard on priority
  (EMPTY_DRAFT.priority is always "medium" and the picker only sets
  non-empty strings, so the fallback can never fire).
- layout: move sonner Toaster to top-right so it stops visually
  overlapping with the custom ToastProvider's bottom-right stack.
- task_http_handlers_test: add regression test asserting that a
  create-task body with project_id (and no workflow_id) reaches the
  office code path and the office workflow auto-resolves. Guards the
  exact wiring that caused the original silent failure.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-05-21 01:14:08 +01:00

96 lines
3.9 KiB
TypeScript

import type { Metadata, Viewport } from "next";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { StateProvider } from "@/components/state-provider";
import { WebSocketConnector } from "@/components/ws-connector";
import { ToastProvider } from "@/components/toast-provider";
import { TooltipProvider } from "@kandev/ui/tooltip";
import { Toaster as SonnerToaster } from "@kandev/ui/sonner";
import { CommandRegistryProvider } from "@/lib/commands/command-registry";
import { CommandPanel } from "@/components/command-panel";
import { GlobalCommands } from "@/components/global-commands";
import { RecentTaskSwitcher } from "@/components/task/recent-task-switcher";
import { DiffWorkerPoolProvider } from "@/components/diff-worker-pool-provider";
import { QuickChatProvider } from "@/components/quick-chat/quick-chat-provider";
import { ConfigChatProvider } from "@/components/config-chat/config-chat-provider";
import { SessionFailureToastBridge } from "@/components/session-failure-toast-bridge";
import { SidebarViewsSyncBridge } from "@/components/sidebar-views-sync-bridge";
import { LogBufferBridge } from "@/components/log-buffer-bridge";
import { getFeatureFlagsAction } from "@/app/actions/features";
export const metadata: Metadata = {
title: "Kandev - AI Kanban",
description: "AI-powered workflow management for developers",
};
export const viewport: Viewport = {
// Enable safe area insets for iOS devices (notch, home indicator)
viewportFit: "cover",
// Prevent iOS auto-zoom on input focus (for app-like experience)
maximumScale: 1,
userScalable: false,
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
// API port injection for dev mode (browser opens at web port, API on backend port).
// In production single-port mode, this is not needed — the client uses
// window.location.origin (same-origin, works for any domain / reverse proxy).
const apiPort = process.env.NEXT_PUBLIC_KANDEV_API_PORT ?? null;
const debugMode = process.env.NEXT_PUBLIC_KANDEV_DEBUG === "true";
// SSR-fetch the deployment's feature flags so the entire client tree
// (including the sidebar nav and gated routes) renders with the correct
// visibility on the first paint. Falls back to all-off when the backend
// is unreachable. See docs/decisions/0007-runtime-feature-flags.md.
const features = await getFeatureFlagsAction();
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta name="apple-mobile-web-app-title" content="Kandev" />
</head>
<body className="antialiased font-sans">
{apiPort || debugMode ? (
<script
dangerouslySetInnerHTML={{
__html: [
apiPort ? `window.__KANDEV_API_PORT = ${JSON.stringify(apiPort)};` : "",
debugMode ? `window.__KANDEV_DEBUG = true;` : "",
]
.filter(Boolean)
.join("\n"),
}}
/>
) : null}
<StateProvider initialState={{ features }}>
<ThemeProvider>
<DiffWorkerPoolProvider>
<TooltipProvider>
<ToastProvider>
<SonnerToaster richColors position="top-right" />
<SessionFailureToastBridge />
<SidebarViewsSyncBridge />
<LogBufferBridge />
<CommandRegistryProvider>
<WebSocketConnector />
<GlobalCommands />
<CommandPanel />
<RecentTaskSwitcher />
<ConfigChatProvider>
<QuickChatProvider>{children}</QuickChatProvider>
</ConfigChatProvider>
</CommandRegistryProvider>
</ToastProvider>
</TooltipProvider>
</DiffWorkerPoolProvider>
</ThemeProvider>
</StateProvider>
</body>
</html>
);
}