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) -----
# Local dev: postgresql://postgres:postgres@localhost:5432/tasks
# Production: dedicated `echodo` user/db on CT 102
# The database name is `echodo` everywhere — local dev, Coolify, and the
# 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"
# ----- Redis (CT 102 shared Redis) -----
@ -79,7 +85,9 @@ MCP_SERVER_PORT=3001
# =====================================================================
POSTGRES_USER=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
REDIS_PORT=6379
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.
> 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.
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).
---
@ -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.
- `.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.
- **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.
---

View file

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