Skip to content

Commit 62c194b

Browse files
committed
refactor: update cloud references and project schema
- Removed Environment and Environment Package references from cloud index and meta files. - Added Project and Project Package references to cloud index and meta files. - Updated object schema for SysProject to remove slug, project_type, and region fields. - Modified project provisioning tests to reflect changes in project schema. - Updated project provisioning service to remove slug and project_type handling. - Refactored environment-related tests to project-related tests in the spec files. - Introduced new Project and Project Package protocol schemas in documentation.
1 parent b0017b4 commit 62c194b

17 files changed

Lines changed: 280 additions & 565 deletions

apps/studio/src/components/new-project-dialog.tsx

Lines changed: 11 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
* organization via `client.projects.create()`.
66
*
77
* Form fields mirror {@link ProvisionProjectRequestSchema}:
8-
* slug, displayName, projectType, region, plan. The `organizationId` and
9-
* `createdBy` fields are injected by the backend from the session.
8+
* displayName (required) + driver. The `organizationId` and `createdBy`
9+
* fields are injected by the backend from the session.
1010
*
1111
* On success, the dialog invokes `onCreated(env)` and closes; the parent
1212
* (ProjectSwitcher) is responsible for reloading the project list
1313
* and navigating into the new project.
1414
*/
1515

1616
import { useEffect, useState } from 'react';
17-
import type { Project, ProjectType } from '@objectstack/spec/cloud';
17+
import type { Project } from '@objectstack/spec/cloud';
1818
import {
1919
Dialog,
2020
DialogContent,
@@ -37,16 +37,6 @@ import { useDrivers, useProvisionProject } from '@/hooks/useProjects';
3737
import { toast } from '@/hooks/use-toast';
3838
import { useActiveOrganizationId, useSession } from '@/hooks/useSession';
3939

40-
const PROJECT_TYPES: { value: ProjectType; label: string; hint: string }[] = [
41-
{ value: 'development', label: 'Development', hint: 'For makers building and iterating' },
42-
{ value: 'test', label: 'Test', hint: 'Automated test runs, throwaway data' },
43-
{ value: 'sandbox', label: 'Sandbox', hint: 'Isolated clone of production' },
44-
{ value: 'preview', label: 'Preview', hint: 'Feature preview / PR environment' },
45-
{ value: 'staging', label: 'Staging', hint: 'Pre-production parity' },
46-
{ value: 'production', label: 'Production', hint: 'Live, customer-facing data' },
47-
{ value: 'trial', label: 'Trial', hint: 'Time-boxed demo workspace' },
48-
];
49-
5040
export interface NewProjectDialogProps {
5141
open: boolean;
5242
onOpenChange: (open: boolean) => void;
@@ -62,10 +52,7 @@ export function NewProjectDialog({
6252
const { drivers, loading: driversLoading } = useDrivers();
6353
const activeOrgId = useActiveOrganizationId();
6454
const { user } = useSession();
65-
const [slug, setSlug] = useState('');
6655
const [displayName, setDisplayName] = useState('');
67-
const [projectType, setProjectType] = useState<ProjectType>('development');
68-
const [region, setRegion] = useState('');
6956
const [driver, setDriver] = useState<string>('');
7057

7158
// Auto-select a sensible default once drivers load: prefer turso, then memory,
@@ -80,16 +67,13 @@ export function NewProjectDialog({
8067
}, [driver, drivers]);
8168

8269
const reset = () => {
83-
setSlug('');
8470
setDisplayName('');
85-
setProjectType('development');
86-
setRegion('');
8771
setDriver('');
8872
};
8973

9074
const handleSubmit = async (e: React.FormEvent) => {
9175
e.preventDefault();
92-
if (!slug.trim()) return;
76+
if (!displayName.trim()) return;
9377
if (!activeOrgId) {
9478
toast({
9579
title: 'No active organization',
@@ -102,16 +86,13 @@ export function NewProjectDialog({
10286
const res = await provision({
10387
organizationId: activeOrgId,
10488
createdBy: user?.id ?? '__session__',
105-
slug: slug.trim(),
106-
displayName: displayName.trim() || undefined,
107-
projectType,
108-
region: region.trim() || undefined,
89+
displayName: displayName.trim(),
10990
driver: driver || undefined,
11091
} as any);
11192
const project = (res?.project ?? res) as Project;
11293
toast({
11394
title: 'Project provisioned',
114-
description: `${project.displayName} (${project.slug}) is ready.`,
95+
description: `${project.displayName} is ready.`,
11596
});
11697
reset();
11798
onOpenChange(false);
@@ -139,56 +120,19 @@ export function NewProjectDialog({
139120

140121
<div className="grid gap-4 py-4">
141122
<div className="grid gap-1.5">
142-
<Label htmlFor="env-slug">
143-
Slug <span className="text-destructive">*</span>
123+
<Label htmlFor="env-name">
124+
Name <span className="text-destructive">*</span>
144125
</Label>
145-
<Input
146-
id="env-slug"
147-
value={slug}
148-
onChange={(e) => setSlug(e.target.value)}
149-
placeholder="dev-alice"
150-
pattern="^[a-z0-9][a-z0-9-]{0,62}$"
151-
required
152-
/>
153-
<p className="text-[11px] text-muted-foreground">
154-
Lowercase letters, numbers, and dashes. Unique per organization.
155-
</p>
156-
</div>
157-
158-
<div className="grid gap-1.5">
159-
<Label htmlFor="env-name">Display name</Label>
160126
<Input
161127
id="env-name"
162128
value={displayName}
163129
onChange={(e) => setDisplayName(e.target.value)}
164130
placeholder="Alice's dev sandbox"
131+
required
132+
autoFocus
165133
/>
166134
</div>
167135

168-
<div className="grid gap-1.5">
169-
<Label>Type</Label>
170-
<Select
171-
value={projectType}
172-
onValueChange={(v) => setProjectType(v as ProjectType)}
173-
>
174-
<SelectTrigger>
175-
<SelectValue />
176-
</SelectTrigger>
177-
<SelectContent>
178-
{PROJECT_TYPES.map((t) => (
179-
<SelectItem key={t.value} value={t.value}>
180-
<div className="flex flex-col">
181-
<span>{t.label}</span>
182-
<span className="text-[11px] text-muted-foreground">
183-
{t.hint}
184-
</span>
185-
</div>
186-
</SelectItem>
187-
))}
188-
</SelectContent>
189-
</Select>
190-
</div>
191-
192136
<div className="grid gap-1.5">
193137
<Label>Driver</Label>
194138
<Select
@@ -225,16 +169,6 @@ export function NewProjectDialog({
225169
for tests; `turso` persists to libSQL.
226170
</p>
227171
</div>
228-
229-
<div className="grid gap-1.5">
230-
<Label htmlFor="env-region">Region (optional)</Label>
231-
<Input
232-
id="env-region"
233-
value={region}
234-
onChange={(e) => setRegion(e.target.value)}
235-
placeholder="us-east-1"
236-
/>
237-
</div>
238172
</div>
239173

240174
<DialogFooter>
@@ -246,7 +180,7 @@ export function NewProjectDialog({
246180
>
247181
Cancel
248182
</Button>
249-
<Button type="submit" disabled={provisioning || !slug.trim()}>
183+
<Button type="submit" disabled={provisioning || !displayName.trim()}>
250184
{provisioning ? 'Provisioning…' : 'Create project'}
251185
</Button>
252186
</DialogFooter>

apps/studio/src/components/project-switcher.tsx

Lines changed: 45 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
* ProjectSwitcher
55
*
66
* Power Platform-style project picker, anchored on the left of the
7-
* site header. Lists every project visible to the current session,
8-
* grouped by `projectType`, and navigates to `/projects/:id/overview`
9-
* when one is selected. Also exposes a "+ New project…" footer that
10-
* opens {@link NewProjectDialog}.
7+
* site header. Lists every project visible to the current session and
8+
* navigates to `/projects/:id/overview` when one is selected. Also
9+
* exposes a "+ New project…" footer that opens {@link NewProjectDialog}.
1110
*
1211
* The switcher is intentionally stateless with respect to the active
1312
* project — the URL is the source of truth, read via
@@ -21,12 +20,10 @@
2120
import { useMemo, useState } from 'react';
2221
import { useNavigate, useParams } from '@tanstack/react-router';
2322
import { ChevronsUpDown, Plus, Search, Check } from 'lucide-react';
24-
import type { Project, ProjectType } from '@objectstack/spec/cloud';
2523
import {
2624
DropdownMenu,
2725
DropdownMenuContent,
2826
DropdownMenuItem,
29-
DropdownMenuLabel,
3027
DropdownMenuSeparator,
3128
DropdownMenuTrigger,
3229
} from '@/components/ui/dropdown-menu';
@@ -35,27 +32,6 @@ import { Button } from '@/components/ui/button';
3532
import { Input } from '@/components/ui/input';
3633
import { useProjects } from '@/hooks/useProjects';
3734
import { NewProjectDialog } from '@/components/new-project-dialog';
38-
import { ProjectBadge } from '@/components/project-badge';
39-
40-
const ENV_TYPE_ORDER: ProjectType[] = [
41-
'production',
42-
'staging',
43-
'sandbox',
44-
'development',
45-
'test',
46-
'preview',
47-
'trial',
48-
];
49-
50-
const ENV_TYPE_LABEL: Record<ProjectType, string> = {
51-
production: 'Production',
52-
staging: 'Staging',
53-
sandbox: 'Sandbox',
54-
development: 'Development',
55-
test: 'Test',
56-
preview: 'Preview',
57-
trial: 'Trial',
58-
};
5935

6036
export function ProjectSwitcher() {
6137
const navigate = useNavigate();
@@ -71,24 +47,13 @@ export function ProjectSwitcher() {
7147
[projects, activeId],
7248
);
7349

74-
const grouped = useMemo(() => {
50+
const filtered = useMemo(() => {
7551
const q = search.trim().toLowerCase();
76-
const filtered = q
77-
? projects.filter(
78-
(e) =>
79-
e.slug.toLowerCase().includes(q) ||
80-
e.displayName.toLowerCase().includes(q) ||
81-
e.id.toLowerCase().includes(q),
82-
)
83-
: projects;
84-
const map = new Map<ProjectType, Project[]>();
85-
for (const e of filtered) {
86-
const arr = map.get(e.projectType) ?? [];
87-
arr.push(e);
88-
map.set(e.projectType, arr);
89-
}
90-
return ENV_TYPE_ORDER.filter((t) => map.has(t)).map(
91-
(t) => [t, map.get(t)!] as const,
52+
if (!q) return projects;
53+
return projects.filter(
54+
(e) =>
55+
e.displayName.toLowerCase().includes(q) ||
56+
e.id.toLowerCase().includes(q),
9257
);
9358
}, [projects, search]);
9459

@@ -111,12 +76,9 @@ export function ProjectSwitcher() {
11176
className="h-8 gap-2 px-2 text-sm font-medium"
11277
>
11378
{active ? (
114-
<>
115-
<span className="max-w-[160px] truncate">
116-
{active.displayName}
117-
</span>
118-
<ProjectBadge projectType={active.projectType} />
119-
</>
79+
<span className="max-w-[160px] truncate">
80+
{active.displayName}
81+
</span>
12082
) : (
12183
<span className="text-muted-foreground">
12284
{loading ? 'Loading projects…' : 'Select project'}
@@ -149,47 +111,40 @@ export function ProjectSwitcher() {
149111
No projects yet.
150112
</div>
151113
)}
152-
{grouped.map(([projectType, list]) => (
153-
<div key={projectType}>
154-
<DropdownMenuLabel className="text-[10px] uppercase tracking-wider text-muted-foreground">
155-
{ENV_TYPE_LABEL[projectType]}
156-
</DropdownMenuLabel>
157-
{list.map((env) => (
158-
<DropdownMenuItem
159-
key={env.id}
160-
onSelect={(e) => {
161-
e.preventDefault();
162-
handleSelect(env.id);
163-
}}
164-
className="flex items-start gap-2"
165-
>
166-
<div className="flex-1 min-w-0">
167-
<div className="flex items-center gap-2">
168-
<span className="truncate font-medium">
169-
{env.displayName}
170-
</span>
171-
{env.isDefault && (
172-
<Badge
173-
variant="outline"
174-
className="h-4 px-1 text-[9px]"
175-
>
176-
default
177-
</Badge>
178-
)}
179-
</div>
180-
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
181-
<code className="font-mono">{env.slug}</code>
182-
{env.region && <span>· {env.region}</span>}
183-
<span>· {env.status}</span>
184-
</div>
185-
</div>
186-
<ProjectBadge projectType={env.projectType} />
187-
{env.id === activeId && (
188-
<Check className="h-3.5 w-3.5 text-primary" />
114+
{filtered.map((env) => (
115+
<DropdownMenuItem
116+
key={env.id}
117+
onSelect={(e) => {
118+
e.preventDefault();
119+
handleSelect(env.id);
120+
}}
121+
className="flex items-start gap-2"
122+
>
123+
<div className="flex-1 min-w-0">
124+
<div className="flex items-center gap-2">
125+
<span className="truncate font-medium">
126+
{env.displayName}
127+
</span>
128+
{env.isDefault && (
129+
<Badge
130+
variant="outline"
131+
className="h-4 px-1 text-[9px]"
132+
>
133+
default
134+
</Badge>
189135
)}
190-
</DropdownMenuItem>
191-
))}
192-
</div>
136+
</div>
137+
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
138+
<code className="font-mono opacity-60">
139+
{env.id.slice(0, 8)}
140+
</code>
141+
<span>· {env.status}</span>
142+
</div>
143+
</div>
144+
{env.id === activeId && (
145+
<Check className="h-3.5 w-3.5 text-primary" />
146+
)}
147+
</DropdownMenuItem>
193148
))}
194149
</div>
195150

apps/studio/src/routes/projects.$projectId.index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
Loader2,
2424
Package,
2525
} from 'lucide-react';
26-
import { ProjectBadge } from '@/components/project-badge';
2726
import { ProjectStatusBadge } from '@/components/project-status-badge';
2827
import { Card } from '@/components/ui/card';
2928
import { Button } from '@/components/ui/button';
@@ -130,7 +129,6 @@ function ProjectOverviewComponent() {
130129
<h1 className="text-2xl font-semibold">
131130
{project.displayName}
132131
</h1>
133-
<ProjectBadge projectType={project.projectType} />
134132
{project.isDefault && (
135133
<Badge variant="outline">default</Badge>
136134
)}
@@ -222,7 +220,7 @@ function ProjectOverviewComponent() {
222220
</Card>
223221
)}
224222

225-
{project.projectType === 'production' && project.status === 'active' && (
223+
{false && project.status === 'active' && (
226224
<Card className="flex items-start gap-3 border-red-500/40 bg-red-500/5 p-4">
227225
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-red-600" />
228226
<div className="text-sm">

apps/studio/src/routes/projects.$projectId.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ function ProjectLayoutComponent() {
8989
// the dialog know which project it's protecting.
9090
useEffect(() => {
9191
registerActiveProject({
92-
projectType: detail?.project?.projectType,
92+
projectType: undefined,
9393
displayName: detail?.project?.displayName,
9494
});
9595
return () => registerActiveProject({ projectType: undefined });

0 commit comments

Comments
 (0)