fix(workspaces): wire top-header create-workspace button + clarify echodo db naming

The Create Workspace button in the top-header dropdown was still calling
`window.alert("Create workspace (placeholder)")` instead of opening the
real CreateWorkspaceDialog. Wires it to the same dialog the sidebar
workspace switcher uses.

Also clarifies in .env.example and AGENTS.md that the canonical Postgres
database name is `echodo` everywhere (local, Coolify, drizzle ledger,
docker-compose POSTGRES_DB). A stale `tasks` database on CT 102 from the
pre-rename era was the root cause of the 2026-06 Authentik "Configuration"
SSO outage: Coolify correctly pointed at `echodo` (which was empty), while
the only initialized schema lived in the relic `tasks` DB.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Randall Stillwell 2026-06-05 16:33:41 -05:00
parent 8b981723b6
commit 011d3eb710
3 changed files with 23 additions and 5 deletions

View file

@ -10,8 +10,14 @@
# ===================================================================== # =====================================================================
# ----- Database (CT 102 shared Postgres) ----- # ----- Database (CT 102 shared Postgres) -----
# Local dev: postgresql://postgres:postgres@localhost:5432/tasks # The database name is `echodo` everywhere — local dev, Coolify, and the
# Production: dedicated `echodo` user/db on CT 102 # drizzle migration ledger all assume it. Do NOT use `tasks` (an early
# pre-rename name); see commit 93565cd and the 2026-06 drift incident for
# why a parallel `tasks` DB caused production OAuth to look broken when
# it was really just connected to an empty schema.
#
# Local dev (Docker compose): postgresql://postgres:postgres@localhost:5432/echodo
# Production (Coolify, CT 102): dedicated `echodo` user/db on shared Postgres
DATABASE_URL="postgresql://echodo:CHANGE_ME@192.168.68.102:5432/echodo" DATABASE_URL="postgresql://echodo:CHANGE_ME@192.168.68.102:5432/echodo"
# ----- Redis (CT 102 shared Redis) ----- # ----- Redis (CT 102 shared Redis) -----
@ -79,7 +85,9 @@ MCP_SERVER_PORT=3001
# ===================================================================== # =====================================================================
POSTGRES_USER=postgres POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=postgres
POSTGRES_DB=tasks # Keep this aligned with the database name in DATABASE_URL above. The
# docker-compose files default to `tasks` (the old name) — override here.
POSTGRES_DB=echodo
POSTGRES_PORT=5432 POSTGRES_PORT=5432
REDIS_PORT=6379 REDIS_PORT=6379
WEB_PORT=3000 WEB_PORT=3000

View file

@ -3,7 +3,7 @@
Operating manual for AI coding agents (Cursor, Codex, Copilot, etc.) working in this repo. Humans should read it too. Keep it short and accurate — if you change conventions in code, update this file in the same change. Operating manual for AI coding agents (Cursor, Codex, Copilot, etc.) working in this repo. Humans should read it too. Keep it short and accurate — if you change conventions in code, update this file in the same change.
> Product: **Echodo** — a multitenant task app whose backlog is mirrored as markdown under `plans/` and synced to Cursor. > Product: **Echodo** — a multitenant task app whose backlog is mirrored as markdown under `plans/` and synced to Cursor.
> Repo name on disk is `tasks`. Package scope is `@tasks/*`. > Repo name on disk is `tasks` and the package scope is `@tasks/*` (historical, pre-rename). Everything else — the database, the deploy, the public domain — is `echodo`. If you see a `tasks` database anywhere, it's an artifact of the old name; the source of truth is `echodo`.
--- ---
@ -55,6 +55,8 @@ Run from the repo root unless noted. Always use `pnpm`, never `npm` or `yarn`.
Before opening a PR or finishing a task, run **`pnpm lint && pnpm type-check && pnpm test`** at minimum. Run `pnpm build` if you touched build config, server entry points, or cross-package exports. Before opening a PR or finishing a task, run **`pnpm lint && pnpm type-check && pnpm test`** at minimum. Run `pnpm build` if you touched build config, server entry points, or cross-package exports.
If you edit anything under `apps/mcp-server/` or any library it imports, your changes won't be visible to MCP tool calls in the **current** Cursor chat — the MCP server is a long-lived stdio process per session and doesn't auto-reload. See the **MCP server lifecycle** section in [config/CursorSync.md](./config/CursorSync.md) for how to find and kill stale servers.
Tests live next to source as `*.test.ts` files. Vitest is configured per package (see `vitest.config.ts` in `packages/shared`, `packages/database`, `packages/ai`). When you add a new package that has logic worth verifying, copy one of those configs and add `test` / `test:watch` scripts to the package's `package.json`. CI gates merges on lint + type-check + test — see [.github/workflows/ci.yml](./.github/workflows/ci.yml). Tests live next to source as `*.test.ts` files. Vitest is configured per package (see `vitest.config.ts` in `packages/shared`, `packages/database`, `packages/ai`). When you add a new package that has logic worth verifying, copy one of those configs and add `test` / `test:watch` scripts to the package's `package.json`. CI gates merges on lint + type-check + test — see [.github/workflows/ci.yml](./.github/workflows/ci.yml).
--- ---
@ -64,6 +66,7 @@ Tests live next to source as `*.test.ts` files. Vitest is configured per package
- Local dev reads `.env` at the repo root. `.env.example` documents every variable; keep it in sync when you add a new one. - Local dev reads `.env` at the repo root. `.env.example` documents every variable; keep it in sync when you add a new one.
- `.env`, `*.secrets`, `*-credentials.*`, `id_rsa`, `*.key`, and `AGENT-DEPLOY.md` are gitignored. **Never** read `AGENT-DEPLOY.md` contents into a committed file, log, or PR description — it contains homelab IPs and credentials. - `.env`, `*.secrets`, `*-credentials.*`, `id_rsa`, `*.key`, and `AGENT-DEPLOY.md` are gitignored. **Never** read `AGENT-DEPLOY.md` contents into a committed file, log, or PR description — it contains homelab IPs and credentials.
- Production deploys run on Coolify (CT 107) against shared Postgres/Redis on CT 102. Don't change deploy assumptions (Docker compose files, `next.config.docker.ts`) without flagging it explicitly. - Production deploys run on Coolify (CT 107) against shared Postgres/Redis on CT 102. Don't change deploy assumptions (Docker compose files, `next.config.docker.ts`) without flagging it explicitly.
- **The Postgres database name is `echodo`**, full stop — local dev, Coolify, the drizzle migration ledger, and the docker-compose `POSTGRES_DB` all need to agree. An older `tasks` database may still exist on CT 102 as a relic of the pre-rename era; treat it as a dev sandbox at best and never point Coolify at it. The 2026-06 "Authentik SSO returns `Configuration`" incident was caused by Coolify pointing at `echodo` (correct) while a stale `tasks` database had the only initialized schema.
- `NEXT_PUBLIC_*` is the only browser-exposed prefix. Never put secrets behind it. - `NEXT_PUBLIC_*` is the only browser-exposed prefix. Never put secrets behind it.
--- ---

View file

@ -1,5 +1,6 @@
"use client"; "use client";
import { useState } from "react";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { signOut } from "next-auth/react"; import { signOut } from "next-auth/react";
import { import {
@ -34,6 +35,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { CreateWorkspaceDialog } from "@/components/workspaces/create-workspace-dialog";
import { useWorkspaceStore } from "@/lib/stores/workspace-store"; import { useWorkspaceStore } from "@/lib/stores/workspace-store";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -48,6 +50,7 @@ export function TopHeader({ onOpenSearch, onQuickAction }: TopHeaderProps) {
const workspace = useWorkspaceStore((s) => s.currentWorkspace); const workspace = useWorkspaceStore((s) => s.currentWorkspace);
const workspaceName = workspace?.name ?? "Workspace"; const workspaceName = workspace?.name ?? "Workspace";
const workspaceId = workspace?.id; const workspaceId = workspace?.id;
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
const initials = workspaceName.trim().slice(0, 2).toUpperCase() || "WS"; const initials = workspaceName.trim().slice(0, 2).toUpperCase() || "WS";
return ( return (
@ -157,7 +160,7 @@ export function TopHeader({ onOpenSearch, onQuickAction }: TopHeaderProps) {
variant="outline" variant="outline"
size="sm" size="sm"
className="w-full gap-1.5 text-xs" className="w-full gap-1.5 text-xs"
onClick={() => window.alert("Create workspace (placeholder)")} onClick={() => setCreateWorkspaceOpen(true)}
> >
<Plus className="size-3.5" /> <Plus className="size-3.5" />
Create Workspace Create Workspace
@ -165,6 +168,10 @@ export function TopHeader({ onOpenSearch, onQuickAction }: TopHeaderProps) {
</div> </div>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<CreateWorkspaceDialog
open={createWorkspaceOpen}
onOpenChange={setCreateWorkspaceOpen}
/>
{/* Center search bar */} {/* Center search bar */}
<div className="flex flex-1 justify-center px-2"> <div className="flex flex-1 justify-center px-2">