diff --git a/apps/web/src/core/managers/project-manager.ts b/apps/web/src/core/managers/project-manager.ts index b38fd50d..8b593fec 100644 --- a/apps/web/src/core/managers/project-manager.ts +++ b/apps/web/src/core/managers/project-manager.ts @@ -5,6 +5,7 @@ import type { TProjectSortKey, TProjectSortOption, TProjectSettings, + TProjectType, TTimelineViewState, } from "@/project/types"; import type { ExportOptions, ExportResult, ExportState } from "@/export"; @@ -79,7 +80,13 @@ export class ProjectManager { await this.storageMigrationPromise; } - async createNewProject({ name }: { name: string }): Promise { + async createNewProject({ + name, + projectType, + }: { + name: string; + projectType?: TProjectType; + }): Promise { const mainScene = buildDefaultScene({ name: "Main scene", isMain: true }); const newProject: TProject = { metadata: { @@ -88,6 +95,7 @@ export class ProjectManager { duration: getProjectDurationFromScenes({ scenes: [mainScene] }), createdAt: new Date(), updatedAt: new Date(), + projectType: projectType ?? "standard", }, scenes: [mainScene], currentSceneId: mainScene.id, diff --git a/apps/web/src/project/__tests__/project-type.test.ts b/apps/web/src/project/__tests__/project-type.test.ts new file mode 100644 index 00000000..a7e35b8d --- /dev/null +++ b/apps/web/src/project/__tests__/project-type.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from "bun:test"; +import { PROJECT_TYPES, type TProjectType } from "@/project/types"; + +describe("PROJECT_TYPES", () => { + test("contains standard and shorts", () => { + expect(PROJECT_TYPES).toContain("standard"); + expect(PROJECT_TYPES).toContain("shorts"); + }); + + test("project metadata uses 'standard' when projectType is undefined", () => { + function buildMetadataProjectType(input?: TProjectType): TProjectType { + return input ?? "standard"; + } + expect(buildMetadataProjectType(undefined)).toBe("standard"); + expect(buildMetadataProjectType("shorts")).toBe("shorts"); + expect(buildMetadataProjectType("standard")).toBe("standard"); + }); + + test("PROJECT_TYPES is stable in declared order", () => { + expect([...PROJECT_TYPES]).toEqual(["standard", "shorts"]); + }); +}); diff --git a/apps/web/src/project/types.ts b/apps/web/src/project/types.ts index dca2d785..0608dd0e 100644 --- a/apps/web/src/project/types.ts +++ b/apps/web/src/project/types.ts @@ -2,6 +2,15 @@ import type { FrameRate } from "opencut-wasm"; import type { TScene } from "@/timeline/types"; import type { MediaTime } from "@/wasm"; +export const PROJECT_TYPES = ["standard", "shorts"] as const; +/** + * Project intent. "shorts" indicates a vertical 9:16 short-form project + * (e.g. YouTube Shorts) — drives preset pickers, export defaults, and + * vertical-safe text positioning. "standard" covers landscape, square, + * and other non-Shorts aspect ratios. + */ +export type TProjectType = (typeof PROJECT_TYPES)[number]; + export type TBackground = | { type: "color"; @@ -24,6 +33,13 @@ export interface TProjectMetadata { duration: MediaTime; createdAt: Date; updatedAt: Date; + /** + * Discriminator for project intent. Optional for back-compat — undefined on + * projects created before this field existed; consumers MUST treat undefined + * as "standard" (e.g. `projectType ?? "standard"`). New projects always set + * this explicitly via ProjectManager.createNewProject. + */ + projectType?: TProjectType; } export interface TProjectSettings {