Full MVP: workspace layout, visual refresh, PWA, production deploy

Major changes since initial commit:

Schema: version branching (parentVersionId, branchLabel), share links,
guest comments, track status enum (sketch/in_progress/final/released),
track sections, cover art for projects and tracks.

API: 29+ endpoints — auth, projects, tracks, versions, comments, share
links (public + management), uploads (cover), activity feed, onboarding
demo seed. Email templates in German with brand styling.

Web: SvelteKit 5 workspace layout with persistent sidebar, breadcrumb
top-bar, collapsible right panel. SoundCloud-style waveform player with
round play button, avatar comment markers, keyboard shortcuts (Space/JKL/C).
Full German UI. Cover art with gradient fallback. Track status pills.
Activity feed dashboard. Welcome modal with demo-seed trigger. Landing
page with 7-section scroll layout. Login on /login. Public /listen/:token
page for guest feedback.

Visual: Inter Variable font, Magenta→Orange gradient accent, warm dark
neutrals, Lucide-style inline SVG icon set, spring animations on modals,
glass-effect toasts, responsive from 360px to 2560px+.

PWA: manifest, service worker, icons, iOS/Android installable.

Production: adapter-node, server-side API proxy hook, docker-compose with
Postgres + MinIO + auto-migration + health checks. Env example included.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Robin Choice
2026-04-10 11:47:48 +02:00
parent 4dc095463f
commit 8bf72c2482
78 changed files with 8216 additions and 1386 deletions

View File

@@ -12,14 +12,14 @@ export const PROJECT_ROLES = [
export type ProjectRole = (typeof PROJECT_ROLES)[number];
export const ROLE_LABELS: Record<ProjectRole, string> = {
owner: 'Owner',
recording_engineer: 'Recording Engineer',
mixing_engineer: 'Mixing Engineer',
mastering_engineer: 'Mastering Engineer',
owner: 'Besitzer',
recording_engineer: 'Aufnahme',
mixing_engineer: 'Mixing',
mastering_engineer: 'Mastering',
artist: 'Artist',
label: 'Label',
management: 'Management',
viewer: 'Viewer',
viewer: 'Nur Zuhören',
};
export const ENGINEER_ROLES: ProjectRole[] = [

View File

@@ -9,6 +9,7 @@ export const createProjectSchema = z.object({
export const updateProjectSchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(2000).optional(),
coverImageUrl: z.string().nullable().optional(),
});
export const inviteMemberSchema = z.object({

View File

@@ -6,9 +6,28 @@ export const createTrackSchema = z.object({
description: z.string().max(2000).optional(),
});
export const TRACK_STATUSES = ['sketch', 'in_progress', 'final', 'released'] as const;
export type TrackStatus = (typeof TRACK_STATUSES)[number];
export const TRACK_STATUS_LABELS: Record<TrackStatus, string> = {
sketch: 'Skizze',
in_progress: 'In Arbeit',
final: 'Final',
released: 'Veröffentlicht',
};
export const updateTrackSchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(2000).optional(),
coverImageUrl: z.string().nullable().optional(),
status: z.enum(TRACK_STATUSES).optional(),
section: z.string().max(100).nullable().optional(),
});
export const coverUploadSchema = z.object({
fileName: z.string().min(1).max(200),
mimeType: z.enum(['image/jpeg', 'image/png', 'image/webp']),
fileSize: z.number().int().positive().max(2 * 1024 * 1024),
});
export const requestUploadUrlSchema = z.object({
@@ -28,6 +47,12 @@ export const createVersionSchema = z.object({
branchLabel: z.string().max(100).optional(),
});
export const updateVersionSchema = z.object({
label: z.string().max(100).nullable().optional(),
notes: z.string().max(2000).nullable().optional(),
branchLabel: z.string().max(100).nullable().optional(),
});
export const createShareLinkSchema = z.object({
expiresAt: z.string().datetime().optional(),
allowComments: z.boolean().optional(),
@@ -46,5 +71,7 @@ export type CreateTrackInput = z.infer<typeof createTrackSchema>;
export type UpdateTrackInput = z.infer<typeof updateTrackSchema>;
export type RequestUploadUrlInput = z.infer<typeof requestUploadUrlSchema>;
export type CreateVersionInput = z.infer<typeof createVersionSchema>;
export type UpdateVersionInput = z.infer<typeof updateVersionSchema>;
export type CreateShareLinkInput = z.infer<typeof createShareLinkSchema>;
export type CoverUploadInput = z.infer<typeof coverUploadSchema>;
export type GuestCommentInput = z.infer<typeof guestCommentSchema>;