feat: add projectType metadata field (standard | shorts)

Adds optional projectType discriminator on TProjectMetadata so downstream features (preset pickers, export defaults, UI affordances) can branch on intent without inferring from canvas dimensions.

- New const PROJECT_TYPES and type TProjectType
- TProjectMetadata.projectType (optional for back-compat with stored projects)
- ProjectManager.createNewProject accepts optional projectType, defaults to 'standard'
- Unit tests for PROJECT_TYPES invariants
This commit is contained in:
kseungyong 2026-05-18 17:41:01 +09:00
parent 6fdb1559aa
commit 7131f88bf4
3 changed files with 47 additions and 1 deletions

View File

@ -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<string> {
async createNewProject({
name,
projectType,
}: {
name: string;
projectType?: TProjectType;
}): Promise<string> {
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,

View File

@ -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"]);
});
});

View File

@ -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 {