From 5eec80bbca0c9ad7a3827679b34bb7c93c4ff4fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Dvo=C5=99=C3=A1k?=
<150935816+Dvorinka@users.noreply.github.com>
Date: Mon, 28 Sep 2026 21:53:27 +0200
Subject: [PATCH] feat(frontend): auto-derive slugs from names on create forms
(#48)
The org/project/bucket/collection create forms required typing a slug
by hand. While the slug field is untouched (empty or still equal to the
derived value), naming the entity fills its slug; a manual slug edit
stops the derivation and is sanitized to slug form as you type.
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---
CHANGELOG.md | 4 ++
apps/frontend/src/App.tsx | 11 ++---
apps/frontend/src/lib/__tests__/slug.test.ts | 45 ++++++++++++++++++++
apps/frontend/src/lib/slug.ts | 27 ++++++++++++
4 files changed, 82 insertions(+), 5 deletions(-)
create mode 100644 apps/frontend/src/lib/__tests__/slug.test.ts
create mode 100644 apps/frontend/src/lib/slug.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1734e30..df2abd7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to Primora. Format follows [Keep a Changelog](https://keepac
## [Unreleased]
+### Added
+
+- **Slugs auto-derive from names** — typing an organization, project, bucket, or collection name now fills its slug (`TDvorak` → `tdvorak`); hand-editing the slug stops the derivation, and manual edits are sanitized as you type. Edit forms stay manual.
+
## [0.8.3] - 2026-09-28
### Added
diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx
index d25f05d..880ffa2 100644
--- a/apps/frontend/src/App.tsx
+++ b/apps/frontend/src/App.tsx
@@ -28,6 +28,7 @@ import { Show, createEffect, createMemo, createResource, createSignal, onCleanup
import { authClient, fetchApiToken } from "./lib/auth-client";
import { configureApiToken, errorMessage } from "./lib/api";
import { isDemoMode, disableDemoMode, enableDemoMode, demoSession, demoService } from "./lib/demo-mode";
+import { applySlugField } from "./lib/slug";
import {
AgentSetupModal,
AppShell,
@@ -1670,14 +1671,14 @@ export default function App() {
label="Organization name"
placeholder="Acme Corporation"
value={organizationInput().name}
- onInput={(e) => setOrganizationInput((c) => ({ ...c, name: e.currentTarget.value }))}
+ onInput={(e) => setOrganizationInput((c) => applySlugField(c, "name", e.currentTarget.value))}
required
/>
setOrganizationInput((c) => ({ ...c, slug: e.currentTarget.value }))}
+ onInput={(e) => setOrganizationInput((c) => applySlugField(c, "slug", e.currentTarget.value))}
required
/>