feat: db change and new workspace slug#3849
Conversation
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
📝 WalkthroughWalkthroughIntroduces a workspace slug across UI, API, and database layers. Adds slug field to DB schemas and migrations with a unique constraint, updates createWorkspace input/return, modifies the onboarding form to collect/validate slug, and seeds test/local data with slug values. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant D as Dashboard (Form)
participant A as createWorkspace (tRPC)
participant DB as Database
U->>D: Enter name and slug
D->>A: mutate({ name, slug })
A->>DB: INSERT INTO workspaces (name, slug, ...)
alt slug unique
DB-->>A: OK (workspace id, slug)
A-->>D: Success
D-->>U: Workspace created
else slug conflict
DB-->>A: UNIQUE constraint error
A-->>D: Error (slug taken)
D-->>U: Show validation error
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Thank you for following the naming conventions for pull request titles! 🙏 |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
tools/local/src/db.ts (1)
28-33: Make seeding idempotent: set slug on duplicate as well.If the
workspacesrow already exists (created before this migration),onDuplicateKeyUpdatewon’t populateslug, leaving itNULL. Seed should converge state by updatingslugtoo.Apply this diff:
.onDuplicateKeyUpdate({ set: { createdAtM: Date.now() } }); + .onDuplicateKeyUpdate({ + set: { createdAtM: Date.now(), slug: "unkey-workspace" }, + });apps/dashboard/lib/trpc/routers/workspace/create.ts (2)
49-53: Pre-check slug uniqueness before creating the tenant to avoid orphaned external records.If the DB insert fails on the unique slug, we’ve already created a tenant via
authProvider.createTenant. Do a quick existence check first to fail fast. Keep the DB unique index as the source of truth to guard against races.Apply this diff:
- const orgId = await authProvider.createTenant({ - name: input.name, - userId, - }); + // Fail fast if slug is already taken (DB unique index remains the final arbiter) + const existingSlug = await db.query.workspaces.findFirst({ + where: (w, { eq }) => eq(w.slug, input.slug), + }); + if (existingSlug) { + throw new TRPCError({ code: "CONFLICT", message: "Workspace slug is already in use" }); + } + + const orgId = await authProvider.createTenant({ + name: input.name, + userId, + });
74-109: Map duplicate-key errors to a 409/CONFLICT and preserve generic fallback.Current catch rethrows a generic 500, which masks user-facing feedback on slug conflicts. Detect the duplicate-key error and return a proper
TRPCErrorwith codeCONFLICT.Apply this diff:
- }) - .catch((_err) => { - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: - "We are unable to create the workspace. Please try again or contact support@unkey.dev", - }); - }); + }) + .catch((err) => { + const code = (err as any)?.code as string | undefined; + const msg = (err as Error)?.message ?? ""; + // MySQL duplicate key + if (code === "ER_DUP_ENTRY" || /workspaces_slug_unique|Duplicate entry/.test(msg)) { + throw new TRPCError({ + code: "CONFLICT", + message: "Workspace slug is already in use", + }); + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + "We are unable to create the workspace. Please try again or contact support@unkey.dev", + }); + });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (8)
apps/api/src/pkg/testutil/harness.ts(2 hunks)apps/dashboard/app/new/hooks/use-workspace-step.tsx(4 hunks)apps/dashboard/lib/trpc/routers/workspace/create.ts(2 hunks)internal/db/drizzle/0001_legal_kat_farrell.sql(1 hunks)internal/db/drizzle/meta/0001_snapshot.json(1 hunks)internal/db/drizzle/meta/_journal.json(1 hunks)internal/db/src/schema/workspaces.ts(1 hunks)tools/local/src/db.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages
Files:
tools/local/src/db.tsinternal/db/src/schema/workspaces.tsapps/api/src/pkg/testutil/harness.tsapps/dashboard/lib/trpc/routers/workspace/create.tsapps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects
Files:
tools/local/src/db.tsinternal/db/src/schema/workspaces.tsapps/api/src/pkg/testutil/harness.tsapps/dashboard/lib/trpc/routers/workspace/create.tsapps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{env,js,ts,go}
📄 CodeRabbit inference engine (CLAUDE.md)
All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME
Files:
tools/local/src/db.tsinternal/db/src/schema/workspaces.tsapps/api/src/pkg/testutil/harness.tsapps/dashboard/lib/trpc/routers/workspace/create.ts
🧠 Learnings (4)
📚 Learning: 2025-04-08T09:34:24.576Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.
Applied to files:
tools/local/src/db.ts
📚 Learning: 2025-08-21T12:37:40.966Z
Learnt from: Flo4604
PR: unkeyed/unkey#3821
File: apps/dashboard/lib/trpc/routers/key/updateRootKeyPermissions.ts:74-74
Timestamp: 2025-08-21T12:37:40.966Z
Learning: Root keys in Unkey have two workspace fields: `workspaceId` (always set to env().UNKEY_WORKSPACE_ID for the Unkey workspace that owns the key) and `forWorkspaceId` (set to ctx.workspace.id for the user's workspace that the key is for). When querying root keys, the system filters by forWorkspaceId to get keys for the current user's workspace, but the returned rootKey.workspaceId is always the Unkey workspace ID.
Applied to files:
tools/local/src/db.tsapps/api/src/pkg/testutil/harness.ts
📚 Learning: 2024-10-04T17:27:09.821Z
Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In `apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx`, the hidden `<input>` elements for `workspaceId` and `keyAuthId` work correctly without being registered with React Hook Form.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2025-07-28T19:42:37.047Z
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/page.tsx:74-81
Timestamp: 2025-07-28T19:42:37.047Z
Learning: In apps/dashboard/app/(app)/projects/page.tsx, the user mcstepp prefers to keep placeholder functions like generateSlug inline during POC/demonstration phases rather than extracting them to utility modules, with plans to refactor later when the feature matures beyond the proof-of-concept stage.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧬 Code graph analysis (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
apps/dashboard/app/new/components/onboarding-wizard.tsx (1)
OnboardingStep(7-39)apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
createWorkspace(11-114)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test API / API Test Local
- GitHub Check: Test Go API Local / Test
- GitHub Check: Build / Build
- GitHub Check: Test Packages / Test
🔇 Additional comments (3)
internal/db/drizzle/meta/_journal.json (1)
12-18: Journal entry looks consistent with the migration.Idx increments, tag matches the SQL filename, and timestamp ordering is coherent. No action needed.
apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
58-59: OK once normalization is in place.With the Zod
.transform((s) => s.toLowerCase()), storingslug: input.slugpreserves canonical lowercase.apps/api/src/pkg/testutil/harness.ts (1)
257-277: Good: seed data now includes slugs matching the new schema.Adding
slug: "unkey-workspace"andslug: "user-workspace"keepsWorkspaceliterals compliant and exercises the new unique index during tests. No issues.
| "workspaces_org_id_unique": { | ||
| "name": "workspaces_org_id_unique", | ||
| "columns": ["org_id"] | ||
| }, | ||
| "workspaces_slug_unique": { | ||
| "name": "workspaces_slug_unique", | ||
| "columns": ["slug"] | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Heads-up: unique index on nullable slug allows multiple NULLs in MySQL.
workspaces_slug_unique won’t prevent multiple rows with slug = NULL. That’s acceptable for a staged rollout, but once backfilled, consider enforcing NOT NULL to ensure uniqueness semantics match expectations.
🤖 Prompt for AI Agents
internal/db/drizzle/meta/0001_snapshot.json lines 1243-1251: the unique index
workspaces_slug_unique is defined on a nullable slug, which in MySQL allows
multiple NULLs; to enforce true uniqueness after rollout, backfill all NULL
slugs (or assign unique placeholder values and resolve duplicates), verify no
duplicate non-NULL slugs remain, then create a migration that alters the slug
column to NOT NULL and (re)applies the unique constraint so the DB enforces
uniqueness; include queries to detect duplicates and a safe backfill/update step
before altering the column.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (3)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
172-186: Avoid hardcoded domain in prefix; read from env/configReplace the literal
app.unkey.com/with an env-driven value to support non-prod environments and white-labeling.Example:
- prefix="app.unkey.com/" + prefix={`${process.env.NEXT_PUBLIC_UNKEY_DASHBOARD_APP_HOST ?? "app.unkey.com"}/`}Per guidelines, prefer env name like
UNKEY_DASHBOARD_APP_HOST(exposed via NEXT_PUBLIC_).
105-108: Unify mutation style (await mutateAsync or use mutate callbacks)You call
mutateAsyncbut don’t await it. Prefer one style to avoid unhandled promise behavior and make control flow explicit.Apply one of:
- createWorkspace.mutateAsync({ + await createWorkspace.mutateAsync({ name: data.workspaceName, slug: data.workspaceUrl.toLowerCase(), });or
- createWorkspace.mutateAsync({ + createWorkspace.mutate({ name: data.workspaceName, slug: data.workspaceUrl.toLowerCase(), });(You already have onSuccess/onError callbacks, so
mutateis a clean fit.)internal/db/drizzle/meta/0001_snapshot.json (1)
1196-1202: workspaces.slug is nullable; uniqueness won’t prevent multiple NULLsFor rollout this is fine, but plan to backfill and then set NOT NULL to have true uniqueness semantics.
Follow-up migration:
- backfill slug for all existing workspaces,
ALTER TABLE workspaces MODIFY slug varchar(64) NOT NULL;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
apps/dashboard/app/new/hooks/use-workspace-step.tsx(4 hunks)internal/db/drizzle/0001_workable_wildside.sql(1 hunks)internal/db/drizzle/meta/0001_snapshot.json(1 hunks)internal/db/drizzle/meta/_journal.json(1 hunks)internal/db/src/schema/workspaces.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages
Files:
internal/db/src/schema/workspaces.tsapps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects
Files:
internal/db/src/schema/workspaces.tsapps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{env,js,ts,go}
📄 CodeRabbit inference engine (CLAUDE.md)
All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME
Files:
internal/db/src/schema/workspaces.ts
🧠 Learnings (2)
📚 Learning: 2025-07-28T19:42:37.047Z
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/page.tsx:74-81
Timestamp: 2025-07-28T19:42:37.047Z
Learning: In apps/dashboard/app/(app)/projects/page.tsx, the user mcstepp prefers to keep placeholder functions like generateSlug inline during POC/demonstration phases rather than extracting them to utility modules, with plans to refactor later when the feature matures beyond the proof-of-concept stage.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2024-10-04T17:27:09.821Z
Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In `apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx`, the hidden `<input>` elements for `workspaceId` and `keyAuthId` work correctly without being registered with React Hook Form.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧬 Code graph analysis (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
apps/dashboard/app/new/components/onboarding-wizard.tsx (1)
OnboardingStep(7-39)apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
createWorkspace(11-114)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test Go API Local / Test
- GitHub Check: Test API / API Test Local
- GitHub Check: Build / Build
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
internal/db/src/schema/workspaces.ts (1)
24-26: Plan NOT NULL and lowercase invariants forslug
- Currently in
internal/db/src/schema/workspaces.tstheslugcolumn is defined aswhich leaves it nullable by default—meaning MySQL will allow multiple rows withslug: varchar("slug", { length: 64 }).unique(),NULLslugs. That’s acceptable for a phased rollout, but we need to backfill existing records and then lock down the schema to prevent futureNULLvalues.- We should also enforce lowercase at the API boundary now, since the database won’t normalize case for us.
Proposed phased plan (apply after data backfill completes):
--- a/internal/db/src/schema/workspaces.ts +++ b/internal/db/src/schema/workspaces.ts @@ export const workspaces = mysqlTable("workspaces", { - slug: varchar("slug", { length: 64 }).unique(), + slug: varchar("slug", { length: 64 }).notNull().unique(),I can draft the backfill script and the
ALTER TABLEmigration when you’re ready.apps/dashboard/app/new/hooks/use-workspace-step.tsx (3)
31-33: Nice: explicit “manually edited” guard for slug generationGood UX improvement to prevent overwriting user input. LGTM.
158-167: Auto-generate slug only when appropriate — looks correctGood checks for empty, not-dirty, and not manually edited before generating. This avoids clobbering user-entered slugs.
193-193: requiredFieldCount updated to 2 — correctMatches required fields (name + URL handle). LGTM.
internal/db/drizzle/meta/0001_snapshot.json (2)
1247-1250: Unique constraint present — good
workspaces_slug_uniqueis in the snapshot and matches the ORM definition.
250-256: Incorrect constraint migration assumptionThe
unique_slug_per_workspace_idxconstraint is created in the initial migration and remains in place after dropping the name constraint, so no additional migration is needed.• In
internal/db/drizzle/0000_broken_blockbuster.sql, thepermissionstable is created with bothCONSTRAINT `unique_name_per_workspace_idx` UNIQUE(`workspace_id`,`name`), CONSTRAINT `unique_slug_per_workspace_idx` UNIQUE(`workspace_id`,`slug`)(lines 47–48)
• Ininternal/db/drizzle/0001_workable_wildside.sql, only the name index is dropped:ALTER TABLE `permissions` DROP INDEX `unique_name_per_workspace_idx`;(line 1)
Because the slug uniqueness constraint already exists from the 0000 migration and is not removed, no “ADD CONSTRAINT unique_slug_per_workspace_idx” step is required.
Likely an incorrect or invalid review comment.
| ALTER TABLE `workspaces` ADD `slug` varchar(64); | ||
| ALTER TABLE `workspaces` ADD CONSTRAINT `workspaces_slug_unique` UNIQUE(`slug`);--> statement-breakpoint |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
🛠️ Refactor suggestion
Workspaces slug DDL looks good; plan backfill + NOT NULL
Adding slug varchar(64) with a unique constraint is correct for a non-breaking rollout. Follow up with:
- backfill slugs for existing rows (slugify(name), ensure uniqueness via suffixing),
- set NOT NULL on slug.
I can draft a backfill script plus ALTER migration that safely handles collisions (e.g., appending “-2”, “-3”, …) and adds a unique index on LOWER(slug) via a generated column if you want case-insensitive guarantees.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
72-106: Map unique-constraint violations to CONFLICT and surface a helpful message.Right now, all DB errors become INTERNAL_SERVER_ERROR. Detect duplicate slug (e.g., Postgres code 23505, constraint
workspaces_slug_unique) and throwCONFLICTwith a precise message so the UI can display “This workspace URL is already taken.”Example (outside the selected range; adapt to your DB client):
try { await db.transaction(async (tx) => { /* ... */ }); } catch (err: any) { // Postgres if (err?.code === "23505" && String(err?.constraint || "").includes("workspaces_slug_unique")) { throw new TRPCError({ code: "CONFLICT", message: "Workspace URL is already in use" }); } throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "We are unable to create the workspace. Please try again or contact support@unkey.dev", }); }If you want, I can wire this up for Drizzle/pg and add a small e2e test. Do you want me to open a follow-up?
♻️ Duplicate comments (2)
apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
15-19: Normalize, trim, and bound slug input; align length constraints with DB (64).Add
.trim()to both fields and enforce.min(3).max(64)onslug. Consider canonicalizing to lowercase at the API boundary so clients can be lax while storage stays canonical. This prevents whitespace-only names, overlong slugs, and mixed-case drift.Apply:
- name: z.string().min(3).max(50), - slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { - message: "Slug must be lowercase alphanumeric, words separated by single hyphens", - }), + name: z.string().trim().min(3).max(50), + slug: z + .string() + .trim() + .min(3) + .max(64) + .transform((s) => s.toLowerCase()) + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { + message: + "Slug must be lowercase alphanumeric, words separated by single hyphens", + }),apps/dashboard/app/new/hooks/use-workspace-step.tsx (1)
105-108: Use a single mutation style: either await mutateAsync or use mutate.You’re calling
mutateAsyncwithout awaiting. Preferawait mutateAsync(...)(and letonErrorhandle UI) or switch tomutate(...). This avoids dangling promises and keeps error handling predictable.- createWorkspace.mutateAsync({ + await createWorkspace.mutateAsync({ name: data.workspaceName, slug: data.workspaceUrl.toLowerCase(), });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/dashboard/app/new/hooks/use-workspace-step.tsx(4 hunks)apps/dashboard/lib/trpc/routers/workspace/create.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages
Files:
apps/dashboard/lib/trpc/routers/workspace/create.tsapps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects
Files:
apps/dashboard/lib/trpc/routers/workspace/create.tsapps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{env,js,ts,go}
📄 CodeRabbit inference engine (CLAUDE.md)
All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME
Files:
apps/dashboard/lib/trpc/routers/workspace/create.ts
🧠 Learnings (4)
📚 Learning: 2025-07-28T19:42:37.047Z
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/page.tsx:74-81
Timestamp: 2025-07-28T19:42:37.047Z
Learning: In apps/dashboard/app/(app)/projects/page.tsx, the user mcstepp prefers to keep placeholder functions like generateSlug inline during POC/demonstration phases rather than extracting them to utility modules, with plans to refactor later when the feature matures beyond the proof-of-concept stage.
Applied to files:
apps/dashboard/lib/trpc/routers/workspace/create.tsapps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2024-10-04T17:27:09.821Z
Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In `apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx`, the hidden `<input>` elements for `workspaceId` and `keyAuthId` work correctly without being registered with React Hook Form.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2025-05-16T16:16:02.286Z
Learnt from: mcstepp
PR: unkeyed/unkey#3258
File: apps/dashboard/components/dashboard/feedback-component.tsx:28-35
Timestamp: 2025-05-16T16:16:02.286Z
Learning: When validating string inputs in forms using Zod, it's best practice to use `.trim()` before length checks to prevent submissions consisting only of whitespace characters, particularly for feedback forms where meaningful content is expected.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2025-07-15T14:25:05.608Z
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/apps/api/routes/v2_keys_create_key/handler.go:353-466
Timestamp: 2025-07-15T14:25:05.608Z
Learning: In the Unkey codebase, input validation for API endpoints is handled at the OpenAPI schema layer, which validates request fields like permission slugs (pattern: "^[a-zA-Z0-9_]+$", length: 1-100 characters) before requests reach the handler code. This validation occurs during the zen.BindBody call in handlers.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧬 Code graph analysis (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
apps/dashboard/app/new/components/onboarding-wizard.tsx (1)
OnboardingStep(7-39)apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
createWorkspace(11-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Test API / API Test Local
- GitHub Check: Test Packages / Test
- GitHub Check: Build / Build
- GitHub Check: Test Go API Local / Test
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (4)
158-167: Nice: respectful auto-generation logic.Only generating a slug when empty/not-dirty/not-manually-edited avoids clobbering user intent. Good UX.
193-193: Required field count = 2 is correct.Matches the schema (name + URL). Keeps the step gating accurate.
214-222: Slugify helper aligns with server rules.Lowercasing, stripping invalid chars, collapsing and trimming hyphens are correct and consistent with the proposed regex. LGTM.
19-26: Align client-side workspace URL validation with server-side rulesThe current client regex
/^(?!-)[a-z0-9-]+(?<!-)$/still permits consecutive hyphens (e.g.foo--bar), whereas our backend enforces single hyphens only via^[a-z0-9]+(?:-[a-z0-9]+)*$. Please verify the server-sideworkspaceUrlvalidation pattern and ensure the client matches it exactly to avoid any drift.• File:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
Lines: 19–26Suggested diff:
- .regex( - /^(?!-)[a-z0-9-]+(?<!-)$/, - "Use lowercase letters, numbers, and hyphens (no leading/trailing hyphens).", - ), + .regex( + /^[a-z0-9]+(?:-[a-z0-9]+)*$/, + "Use lowercase letters and numbers, separated by single hyphens.", + ),
|
overall looks good, can you create a preview branch in pscale with this? |
|
Mike doesn’t have access to Planetscale to create branches. @chronark |
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (3)
33-33: Let auto-generation resume when the slug input is clearedIf the user deletes the slug,
slugManuallyEditedstays true and blocks auto-generate on name blur. Reset the flag when empty.- onChange={(evt) => { - // Mark slug as manually edited when user changes it - if (evt.currentTarget.value) { - setSlugManuallyEdited(true); - } - }} + onChange={(evt) => { + const v = evt.currentTarget.value; + // Mark manually edited if non-empty; reset when cleared + setSlugManuallyEdited(v.length > 0); + }}Also applies to: 181-186
106-109: Use a single mutation style (callbacks) to avoid unhandled promise rejectionsYou’re calling
mutateAsyncwithout awaiting/catching. Either await with try/catch or usemutateand rely on onSuccess/onError (already defined).- createWorkspace.mutateAsync({ + createWorkspace.mutate({ name: data.workspaceName, slug: data.slug.toLowerCase(), });
173-187: Rename label to “slug” and externalize the URL prefix
- Label still says “Workspace URL handle”; switch to “Workspace slug” per backend/DB naming.
- Avoid hardcoding
app.unkey.com/; derive from env to work across environments.- label="Workspace URL handle" + label="Workspace slug" @@ - prefix="app.unkey.com/" + prefix={`${process.env.NEXT_PUBLIC_UNKEY_DASHBOARD_APP_HOST ?? "app.unkey.com"}/`}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages
Files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects
Files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧠 Learnings (4)
📚 Learning: 2025-07-28T19:42:37.047Z
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/page.tsx:74-81
Timestamp: 2025-07-28T19:42:37.047Z
Learning: In apps/dashboard/app/(app)/projects/page.tsx, the user mcstepp prefers to keep placeholder functions like generateSlug inline during POC/demonstration phases rather than extracting them to utility modules, with plans to refactor later when the feature matures beyond the proof-of-concept stage.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2024-10-04T17:27:09.821Z
Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In `apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx`, the hidden `<input>` elements for `workspaceId` and `keyAuthId` work correctly without being registered with React Hook Form.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2025-05-16T16:16:02.286Z
Learnt from: mcstepp
PR: unkeyed/unkey#3258
File: apps/dashboard/components/dashboard/feedback-component.tsx:28-35
Timestamp: 2025-05-16T16:16:02.286Z
Learning: When validating string inputs in forms using Zod, it's best practice to use `.trim()` before length checks to prevent submissions consisting only of whitespace characters, particularly for feedback forms where meaningful content is expected.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2025-07-15T14:25:05.608Z
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/apps/api/routes/v2_keys_create_key/handler.go:353-466
Timestamp: 2025-07-15T14:25:05.608Z
Learning: In the Unkey codebase, input validation for API endpoints is handled at the OpenAPI schema layer, which validates request fields like permission slugs (pattern: "^[a-zA-Z0-9_]+$", length: 1-100 characters) before requests reach the handler code. This validation occurs during the zen.BindBody call in handlers.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧬 Code graph analysis (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
apps/dashboard/app/new/components/onboarding-wizard.tsx (1)
OnboardingStep(6-38)apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
createWorkspace(11-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Test Go API Local / Test
- GitHub Check: Test API / API Test Local
- GitHub Check: Test Packages / Test
- GitHub Check: Build / Build
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (3)
159-168: Good guard: don’t overwrite a user-typed slugOnly generating when empty, not dirty, and not manually edited is the right UX.
195-195: requiredFieldCount = 2 is correctMatches the two required fields (name, slug).
215-223: Slugify matches the schema (lowercase, sanitize, collapse, trim)Implementation aligns with the allowed charset and hyphen rules. Keeping it inline is fine given prior preference during POC phases.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (3)
106-109: Don’t call mutateAsync without await; use mutate since you rely on callbacks.Keeps a single style and avoids dangling promises.
- createWorkspace.mutateAsync({ + createWorkspace.mutate({ name: data.workspaceName, slug: data.slug.toLowerCase(), });
173-180: Externalize the URL prefix, rename label to “Workspace slug”, and disable after create.Improves clarity and env portability; prevents post-create edits.
- label="Workspace URL handle" + label="Workspace slug" required error={form.formState.errors.slug?.message} - prefix="app.unkey.com/" + prefix={`${process.env.NEXT_PUBLIC_UNKEY_DASHBOARD_APP_HOST ?? "app.unkey.com"}/`} maxLength={64} + disabled={isLoading || workspaceCreated}
215-223: Slugify looks correct; consider sharing a single schema/util to avoid drift.Optionally extract a shared
workspaceSlugSchema/slugifyin a common package and reuse on client/server.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages
Files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects
Files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧠 Learnings (3)
📚 Learning: 2025-07-28T19:42:37.047Z
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/page.tsx:74-81
Timestamp: 2025-07-28T19:42:37.047Z
Learning: In apps/dashboard/app/(app)/projects/page.tsx, the user mcstepp prefers to keep placeholder functions like generateSlug inline during POC/demonstration phases rather than extracting them to utility modules, with plans to refactor later when the feature matures beyond the proof-of-concept stage.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2024-10-04T17:27:09.821Z
Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In `apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx`, the hidden `<input>` elements for `workspaceId` and `keyAuthId` work correctly without being registered with React Hook Form.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2025-05-16T16:16:02.286Z
Learnt from: mcstepp
PR: unkeyed/unkey#3258
File: apps/dashboard/components/dashboard/feedback-component.tsx:28-35
Timestamp: 2025-05-16T16:16:02.286Z
Learning: When validating string inputs in forms using Zod, it's best practice to use `.trim()` before length checks to prevent submissions consisting only of whitespace characters, particularly for feedback forms where meaningful content is expected.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧬 Code graph analysis (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
apps/dashboard/app/new/components/onboarding-wizard.tsx (1)
OnboardingStep(6-38)apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
createWorkspace(11-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Build / Build
- GitHub Check: Test Go API Local / Test
- GitHub Check: Test API / API Test Local
- GitHub Check: Test Packages / Test
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
159-168: Auto-fill respects user intent — LGTM.Only generates when slug is empty, not dirty, and not manually edited. Good.
194-194: Required fields count updated — LGTM.Matches the two required inputs.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
173-185: Rename label to “Workspace slug”, externalize prefix, and disable during submit.
Keeps terminology consistent; avoids hardcoded host; prevents edits while loading.<FormInput {...form.register("slug")} placeholder="enter-a-handle" - label="Workspace URL handle" + label="Workspace slug" required error={form.formState.errors.slug?.message} - prefix="app.unkey.com/" + prefix={`${process.env.NEXT_PUBLIC_UNKEY_DASHBOARD_APP_HOST ?? "app.unkey.com"}/`} maxLength={64} + disabled={isLoading || workspaceCreated} onChange={(evt) => { const v = evt.currentTarget.value; setSlugManuallyEdited(v.length > 0); }} />
106-109: Avoid unawaited mutateAsync; use a single mutation style.
You’re using onSuccess/onError callbacks; prefer mutate(...) for consistency.Apply:
- createWorkspace.mutateAsync({ + createWorkspace.mutate({ name: data.workspaceName, slug: data.slug.toLowerCase(), });Alternative: await mutateAsync in try/catch and remove the callbacks.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages
Files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects
Files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧠 Learnings (3)
📚 Learning: 2025-07-28T19:42:37.047Z
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/page.tsx:74-81
Timestamp: 2025-07-28T19:42:37.047Z
Learning: In apps/dashboard/app/(app)/projects/page.tsx, the user mcstepp prefers to keep placeholder functions like generateSlug inline during POC/demonstration phases rather than extracting them to utility modules, with plans to refactor later when the feature matures beyond the proof-of-concept stage.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2024-10-04T17:27:09.821Z
Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In `apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx`, the hidden `<input>` elements for `workspaceId` and `keyAuthId` work correctly without being registered with React Hook Form.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
📚 Learning: 2025-05-16T16:16:02.286Z
Learnt from: mcstepp
PR: unkeyed/unkey#3258
File: apps/dashboard/components/dashboard/feedback-component.tsx:28-35
Timestamp: 2025-05-16T16:16:02.286Z
Learning: When validating string inputs in forms using Zod, it's best practice to use `.trim()` before length checks to prevent submissions consisting only of whitespace characters, particularly for feedback forms where meaningful content is expected.
Applied to files:
apps/dashboard/app/new/hooks/use-workspace-step.tsx
🧬 Code graph analysis (1)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (2)
apps/dashboard/app/new/components/onboarding-wizard.tsx (1)
OnboardingStep(6-38)apps/dashboard/lib/trpc/routers/workspace/create.ts (1)
createWorkspace(11-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test Packages / Test
- GitHub Check: Build / Build
- GitHub Check: Test Go API Local / Test
- GitHub Check: Test API / API Test Local
- GitHub Check: autofix
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
apps/dashboard/app/new/hooks/use-workspace-step.tsx (5)
19-27: LGTM: solid slug schema (trim/min/max + single-hyphen regex).
Matches the intended constraints and avoids lookbehind pitfalls.
33-33: Manual-edit flag: good UX guard.
The separate flag plus dirty check avoids accidental overwrites.
192-192: requiredFieldCount update looks correct.
Matches the two required fields in the schema.
213-221: Slugify is consistent with the schema; keeping it inline is fine.
Aligns with the shared rules; inline helper matches prior preference for POCs.
19-27: The root directory listing is needed to locate where the server-side slug validation lives. Please run the above to show top‐level folders so we can search for “slug” or Zod schemas on the backend.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/db/drizzle/0000_dazzling_colonel_america.sql (1)
178-199: Operational sequencing: run migration before app deploy.Follow chronark’s note: create a pscale preview branch, apply migration, validate, then open a deploy request before merging.
♻️ Duplicate comments (1)
internal/db/src/schema/workspaces.ts (1)
22-24: Good addition; align future invariant to non-null + lowercase.After backfilling slugs, consider making it non-null. Also ensure API normalizes to lowercase and validates allowed characters.
Apply after backfill:
- slug: varchar("slug", { length: 64 }).unique(), + slug: varchar("slug", { length: 64 }).notNull().unique(),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
go/pkg/db/schema.sql(2 hunks)internal/db/drizzle/0000_dazzling_colonel_america.sql(3 hunks)internal/db/drizzle/meta/0000_snapshot.json(3 hunks)internal/db/drizzle/meta/_journal.json(1 hunks)internal/db/src/schema/workspaces.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,jsx,ts,tsx}: Use Biome for formatting and linting in TypeScript/JavaScript projects
Prefer named exports over default exports in TypeScript/JavaScript, except for Next.js pages
Files:
internal/db/src/schema/workspaces.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Follow strict TypeScript configuration
Use Zod for runtime validation in TypeScript projects
Files:
internal/db/src/schema/workspaces.ts
**/*.{env,js,ts,go}
📄 CodeRabbit inference engine (CLAUDE.md)
All environment variables must follow the format: UNKEY_<SERVICE_NAME>_VARNAME
Files:
internal/db/src/schema/workspaces.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Test API / API Test Local
- GitHub Check: Test Packages / Test
- GitHub Check: Build / Build
- GitHub Check: Test Go API Local / Test
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
go/pkg/db/schema.sql (2)
182-183: Workspace slug column and unique constraint look correct (global uniqueness).Length 64 matches the TS schema; UNIQUE(slug) creates the needed lookup index.
Also applies to: 197-199
197-199: Confirm intent: global slug uniqueness vs. per-org.UNIQUE(slug) enforces global slugs across all orgs. If slugs were intended to be namespaced per org, switch to UNIQUE(org_id, slug) instead.
internal/db/drizzle/meta/0000_snapshot.json (1)
1130-1136: Snapshot matches DB: workspaces.slug varchar(64) nullable.Looks consistent with Go and TS schemas.
internal/db/drizzle/0000_dazzling_colonel_america.sql (1)
182-199: Migration adds slug and unique constraint correctly.Matches snapshot and app schema; additive and safe with nullable column.
| `slug` varchar(64), | ||
| `partition_id` varchar(256), |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Plan a follow-up to make slug required and canonicalized.
Slug is nullable; if every workspace should have a URL slug, backfill existing rows, then add NOT NULL and enforce lowercase at the API boundary.
I can draft the migration plan if helpful.
🤖 Prompt for AI Agents
In go/pkg/db/schema.sql around lines 182-183, `slug` is currently nullable; plan
and implement a migration to make it required and canonicalized: first write a
backfill script that generates slugs for existing rows (e.g., derive from
workspace name or id), normalizes to lowercase and URL-safe format, and ensures
uniqueness (add suffixes if necessary); run and verify the backfill; then add a
schema migration to ALTER TABLE to set `slug` NOT NULL and create a unique index
on `slug`; finally enforce lowercase/canonicalization at the API boundary for
all new/updated workspaces (validate and transform input to lowercase and
URL-safe form before inserting/updating).
| `slug` varchar(64), | ||
| `partition_id` varchar(256), |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Case handling and URL safety.
If slugs appear in URLs, enforce lowercase and character set in the API layer; DB collation might already be case-insensitive, but explicit normalization avoids surprises.
| "when": 1756630515466, | ||
| "tag": "0000_dazzling_colonel_america", | ||
| "breakpoints": true |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Journal tag updated—ensure migration is wired into deploy process.
Confirm a PlanetScale preview branch is created and the migration is applied before deploying code that writes/reads slug.
🤖 Prompt for AI Agents
In internal/db/drizzle/meta/_journal.json around lines 8 to 10, the journal tag
was updated to "0000_dazzling_colonel_america" but the deployment pipeline may
not run the corresponding migration; ensure the migration is wired into the
deploy process by (1) creating a PlanetScale preview branch for this change and
applying the migration there, (2) updating CI/CD pipeline steps to create/use
that preview branch and run the migration step before any code that reads/writes
the slug is deployed, (3) adding a verification step in CI that checks the
migration ran successfully (e.g., using a migration status command or simple
schema check), and (4) updating the deploy docs/README with the new
preview-branch + migration steps so future deploys follow the same flow.
| "workspaces_org_id_unique": { | ||
| "name": "workspaces_org_id_unique", | ||
| "columns": ["org_id"] | ||
| }, | ||
| "workspaces_slug_unique": { | ||
| "name": "workspaces_slug_unique", | ||
| "columns": ["slug"] | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Unique constraint captured; consider eventual NOT NULL.
Keep as-is now; after backfill, flip slug to notNull in schema + follow-up migration.
🤖 Prompt for AI Agents
internal/db/drizzle/meta/0000_snapshot.json lines 1243-1250: the snapshot
records a unique index on "slug" but the schema still allows NULL; after
performing the planned backfill you should update the schema to mark the slug
column as notNull and add a follow-up migration that alters the column to NOT
NULL (or recreates it appropriately), ensure the backfill has completed for all
rows before running the migration, include a migration down/rollback step if
appropriate, and add/adjust tests to validate the new NOT NULL constraint.
| // slug is used for the workspace URL | ||
| slug: varchar("slug", { length: 64 }).unique(), | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify runtime validation at the API boundary.
Ensure createWorkspace Zod schema enforces: lowercase, [a-z0-9-], no leading/trailing hyphen, max 64.
Run to confirm:
🏁 Script executed:
#!/bin/bash
rg -nP -C2 --type tsx --type ts '\bslug\b' apps/dashboard/lib/trpc/routers/workspace/create.ts apps/dashboard/app/new/hooks/use-workspace-step.tsx
rg -nP -C2 --type ts 'z\.object\([^)]*slug[^)]*\)' apps/dashboard/lib/trpc/routers/workspace/create.ts
rg -nP -C2 --type ts '(toLowerCase|slugify)' apps/dashboard/lib/trpc/routers/workspace/create.ts apps/dashboard/app/new/hooks/use-workspace-step.tsxLength of output: 2026
Add slug validation in createWorkspace schema
In apps/dashboard/lib/trpc/routers/workspace/create.ts the .input() Zod object doesn’t validate slug at runtime. Enforce lowercase alphanumerics and hyphens only (no leading/trailing hyphen) and max 64 chars, for example:
slug: z
.string()
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "invalid slug")
.max(64),🤖 Prompt for AI Agents
internal/db/src/schema/workspaces.ts around lines 22 to 24: the workspace slug
must be validated at runtime in the createWorkspace input schema; update the Zod
input in apps/dashboard/lib/trpc/routers/workspace/create.ts to validate slug as
a string matching lowercase alphanumerics and single hyphen separators (no
leading/trailing hyphen) and max 64 chars by using
.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "invalid slug").max(64) so runtime
inputs cannot bypass the DB constraint.
What does this PR do?
Fixes # (issue)
If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists
Type of change
How should this be tested?
Checklist
Required
pnpm buildpnpm fmtconsole.logsgit pull origin mainAppreciated
Summary by CodeRabbit
New Features
Chores