fix: move to single next js repo
This commit is contained in:
parent
7d0de13d42
commit
8aa1f4e6b7
|
|
@ -1,119 +0,0 @@
|
|||
# Code Review Checklist
|
||||
|
||||
Review every point below carefully to ensure files follow consistent code style and best practices.
|
||||
|
||||
---
|
||||
|
||||
## Function Signatures & Parameters
|
||||
|
||||
- [ ] Every function accepts a single object parameter with destructuring in the signature (for readability and future extensibility)
|
||||
- Exception: tiny one-liner callbacks (e.g. `array.find(x => ...)`, `map`, `filter`, `sort`) do not need destructuring if it hurts readability
|
||||
|
||||
```tsx
|
||||
// ❌ wrong
|
||||
function formatTime(seconds: number, fps: number) { ... }
|
||||
|
||||
// ✅ correct
|
||||
function formatTime({ seconds, fps }: { seconds: number; fps: number }) { ... }
|
||||
```
|
||||
|
||||
## TypeScript & Type Safety
|
||||
|
||||
- [ ] No `any` references
|
||||
- [ ] General interfaces are in the `types` folder, not scattered in components
|
||||
- Example: `TimelineTrack` interface belongs in `src/types/timeline.ts`, not `src/components/timeline/index.tsx`
|
||||
|
||||
## JSX & Components
|
||||
|
||||
- [ ] JSX is clean — no comments explaining what each part does
|
||||
- [ ] Complex/reusable JSX is extracted into sub-components (placed below the main component)
|
||||
- [ ] Components shared across multiple files are in separate files
|
||||
- [ ] File order: constants specific to file (top) -> utils specific to file -> main component → sub-components (bottom)
|
||||
- [ ] Components render UI only — domain logic lives in hooks, utilities, or managers
|
||||
- Simple interaction logic (gestures, modifier keys) can stay if not complex
|
||||
|
||||
## Code Organization & File Structure
|
||||
|
||||
- [ ] Each file has one single purpose/responsibility
|
||||
- Example: `timeline/index.tsx` should not define `validateElementTrackCompatibility` — that belongs in a lib file
|
||||
- Example: `lib/timeline-utils.ts` should not declare `TRACK_COLORS` — that belongs in `constants/`
|
||||
- [ ] Business logic lives in either `src/lib`, `src/core` or `src/services` folder
|
||||
|
||||
## Comments
|
||||
|
||||
- [ ] No AI comments — only human comments that explain _why_, not _what_
|
||||
- Bad: changelog-style comments, explaining readable code, using more words than necessary
|
||||
- [ ] All comments are lowercase
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- [ ] Readability over brevity — use `element` not `el`, `event` not `e`
|
||||
- [ ] Booleans are named `isSomething`, `hasSomething`, or `shouldSomething` — not `something`
|
||||
- [ ] No title case in text/UI — use `Hello world` not `Hello World`
|
||||
|
||||
## Tailwind & Styling
|
||||
|
||||
- [ ] Use `gap-*` instead of `mb-*` or `mt-*` for consistent spacing
|
||||
- [ ] Use `size-*` instead of `h-* w-*` when width and height are the same
|
||||
- [ ] When using `size-*` on icons inside `<Button>`, use `!` modifier to override default `size-4`
|
||||
```tsx
|
||||
<Button>
|
||||
<PlusIcon className="!size-6" /> {/* ✅ correct */}
|
||||
<PlusIcon className="size-6" /> {/* ❌ wrong */}
|
||||
<PlusIcon className="!size-4" /> {/* ❌ unnecessary, size-4 is default */}
|
||||
</Button>
|
||||
```
|
||||
|
||||
## State Management (Zustand)
|
||||
|
||||
- [ ] React components never use `someStore.getState()` — use the `useSomeStore` hook instead
|
||||
- [ ] Store/manager methods are not passed as props — sub-components access them directly
|
||||
|
||||
```tsx
|
||||
// ❌ wrong
|
||||
function Parent() {
|
||||
const { selectedElements } = useTimelineStore();
|
||||
return <Child selectedElements={selectedElements} />;
|
||||
}
|
||||
|
||||
// ✅ correct
|
||||
function Parent() {
|
||||
return <Child />;
|
||||
}
|
||||
function Child() {
|
||||
const { selectedElements } = useTimelineStore();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Components and hooks should use the `useEditor` hook. Only use `EditorCore.getInstance()` if you are outside of a react component/hook. Eg: in a utility function, event handler.
|
||||
|
||||
## Code Quality
|
||||
|
||||
- [ ] Code is scannable — use variables and helper functions to make intent clear at a glance
|
||||
- [ ] Complex logic is extracted into well-named variables or helpers
|
||||
- [ ] No redundant single/plural function variants — if a function can operate on multiple items, it should accept an array and handle both cases. Don't create `doThing()` + `doThings()`.
|
||||
|
||||
```tsx
|
||||
// ❌ wrong — redundant variants
|
||||
function updateElement({ element }: { element: Element }) { ... }
|
||||
function updateElements({ elements }: { elements: Element[] }) { ... }
|
||||
|
||||
// ✅ correct — one function, accepts array
|
||||
function updateElements({ elements }: { elements: Element[] }) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Function Keywords
|
||||
|
||||
| Context | Keyword |
|
||||
| --------------------------------- | ------------------------- |
|
||||
| Next.js page components | `export default function` |
|
||||
| Main react component | `export function` |
|
||||
| Sub-components | `function` |
|
||||
| Utility functions | `export function` |
|
||||
| Functions inside react components | `const` |
|
||||
|
||||
---
|
||||
|
||||
> Every decision, every edit must be carefully considered. Everything matters.
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Comment Guidelines
|
||||
|
||||
## Good Comments (Human-style)
|
||||
- Explain WHY, not WHAT
|
||||
- Document non-obvious behavior or edge cases
|
||||
- Warn about performance implications or side effects
|
||||
- Explain business logic that isn't clear from code
|
||||
|
||||
Examples:
|
||||
```javascript
|
||||
// transfer, not copy; sender buffer detaches
|
||||
// satisfies: check shape; keep literals
|
||||
// keep multibyte across chunks
|
||||
// timingSafeEqual throws on length mismatch
|
||||
```
|
||||
|
||||
## Bad Comments (AI-style)
|
||||
- Don't explain what the code literally does
|
||||
- Don't add changelog-style comments in code
|
||||
- Don't comment every line or obvious operations
|
||||
|
||||
Avoid:
|
||||
```javascript
|
||||
// Prevent duplicate initialization
|
||||
// Check if project is already loaded
|
||||
// Mark as initializing to prevent race conditions
|
||||
// (changed from blah to blah)
|
||||
```
|
||||
|
||||
## Rule
|
||||
Only add comments when there's genuinely non-obvious behavior, performance considerations, or business logic that needs context. Code should be self-documenting through naming and structure.
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Handling Uncertainty
|
||||
|
||||
## Principle
|
||||
If you can't confidently respond due to missing context, data access, or ambiguity (multiple interpretations), report it instead of guessing. Seek clarification to avoid errors.
|
||||
|
||||
Apply when: query lacks details, no access to info/tools, or unclear intent.
|
||||
|
||||
## How to Report
|
||||
1. **Description**: Why uncertain and what you need.
|
||||
2. **Questions**: 1-3 targeted ones.
|
||||
3. **Assumptions** (opt.): State if proceeding; omit otherwise.
|
||||
|
||||
Direct and concise.
|
||||
|
||||
**Assumptions**: None.
|
||||
|
||||
Builds transparency.
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Readability First
|
||||
|
||||
Optimize code for AI agents to understand and modify.
|
||||
|
||||
Never abbreviate. `event` not `e`, `element` not `el`. If it's easy to read, it's correct. In this case, "config" is better than "configuration" because it's shorter and is **still very readable**. "El" is not very readable.
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Separation of Concerns
|
||||
|
||||
## Core Principle
|
||||
|
||||
Each file should have one single purpose/responsibility. Related functionality should be grouped together, unrelated functionality should be separated.
|
||||
|
||||
## Good Separation
|
||||
|
||||
- One file per major concern (auth, validation, data transformation)
|
||||
- Group related utilities together
|
||||
- Extract shared logic into dedicated files
|
||||
- Keep API routes focused on their specific endpoint logic
|
||||
|
||||
Examples:
|
||||
|
||||
```javascript
|
||||
// ✅ Good: Each file has clear responsibility
|
||||
/lib/rate-limit.ts // Rate limiting utilities
|
||||
/lib/validation.ts // Input validation schemas
|
||||
/lib/freesound-api.ts // External API integration
|
||||
/api/sounds/search/route.ts // Route handler only
|
||||
```
|
||||
|
||||
## Bad Mixing of Concerns
|
||||
|
||||
Avoid cramming multiple responsibilities into one file:
|
||||
|
||||
```javascript
|
||||
// ❌ Bad: Route file doing everything
|
||||
/api/sounds/search/route.ts
|
||||
- Rate limiting logic
|
||||
- Validation schemas
|
||||
- API transformation
|
||||
- External API calls
|
||||
- Response formatting
|
||||
- Error handling utilities
|
||||
```
|
||||
|
||||
## When to Separate
|
||||
|
||||
- File is getting long (>500 lines)
|
||||
- Multiple distinct responsibilities in one file
|
||||
- Logic could be reused elsewhere
|
||||
- Complex utilities that distract from main purpose
|
||||
|
||||
## Rule
|
||||
|
||||
One file, one responsibility. Extract shared concerns into focused utility files
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
---
|
||||
description: Ultracite Rules - AI-Ready Formatter and Linter
|
||||
globs: "**/*.{ts,tsx,js,jsx}"
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Project Context
|
||||
|
||||
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's formatter.
|
||||
|
||||
## Key Principles
|
||||
|
||||
- Zero configuration required
|
||||
- Subsecond performance
|
||||
- Maximum type safety
|
||||
- AI-friendly code generation
|
||||
|
||||
## Before Writing Code
|
||||
|
||||
1. Analyze existing patterns in the codebase
|
||||
2. Consider edge cases and error scenarios
|
||||
3. Follow the rules below strictly
|
||||
4. Validate accessibility requirements
|
||||
5. Avoid code duplication
|
||||
|
||||
## Rules
|
||||
|
||||
### Accessibility (a11y)
|
||||
|
||||
- Always include a `title` element for icons unless there's text beside the icon.
|
||||
- Always include a `type` attribute for button elements.
|
||||
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
|
||||
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
|
||||
|
||||
### Code Complexity and Quality
|
||||
|
||||
- Don't use primitive type aliases or misleading types.
|
||||
- Don't use the comma operator.
|
||||
- Use for...of statements instead of Array.forEach.
|
||||
- Don't initialize variables to undefined.
|
||||
- Use .flatMap() instead of map().flat() when possible.
|
||||
|
||||
### React and JSX Best Practices
|
||||
|
||||
- Don't import `React` itself.
|
||||
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
|
||||
- Don't insert comments as text nodes.
|
||||
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
|
||||
|
||||
### Function Parameters and Props
|
||||
|
||||
- Always use destructured props objects instead of individual parameters in functions.
|
||||
- Example: `function helloWorld({ prop }: { prop: string })` instead of `function helloWorld(param: string)`.
|
||||
- This applies to all functions, not just React components.
|
||||
|
||||
### Correctness and Safety
|
||||
|
||||
- Don't assign a value to itself.
|
||||
- Avoid unused imports and variables.
|
||||
- Don't use await inside loops.
|
||||
- Don't hardcode sensitive data like API keys and tokens.
|
||||
- Don't use the TypeScript directive @ts-ignore.
|
||||
- Make sure the `preconnect` attribute is used when using Google Fonts.
|
||||
- Don't use the `delete` operator.
|
||||
- Don't use `require()` in TypeScript/ES modules - use proper `import` statements.
|
||||
|
||||
### TypeScript Best Practices
|
||||
|
||||
- Don't use TypeScript enums.
|
||||
- Use either `T[]` or `Array<T>` consistently.
|
||||
- Don't use the `any` type.
|
||||
|
||||
### Style and Consistency
|
||||
|
||||
- Don't use global `eval()`.
|
||||
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
|
||||
- Don't use `else` blocks when the `if` block breaks early.
|
||||
- Put default function parameters and optional function parameters last.
|
||||
- Use `new` when throwing an error.
|
||||
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
|
||||
|
||||
### Next.js Specific Rules
|
||||
|
||||
- Don't use `<img>` elements in Next.js projects.
|
||||
- Don't use `<head>` elements in Next.js projects.
|
||||
|
||||
## Example: Error Handling
|
||||
|
||||
```typescript
|
||||
// ✅ Good: Comprehensive error handling
|
||||
try {
|
||||
const result = await fetchData();
|
||||
return { success: true, data: result };
|
||||
} catch (error) {
|
||||
console.error("API call failed:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
|
||||
// ❌ Bad: Swallowing errors
|
||||
try {
|
||||
return await fetchData();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
```
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Scannable Code Guidelines/Separating Concerns.
|
||||
|
||||
## Core Principle
|
||||
|
||||
Code should be scannable through proper abstraction, not comments. Use variables and helper functions to make intent clear at a glance.
|
||||
|
||||
## Good Scannable Code
|
||||
|
||||
- Extract complex logic into well-named variables
|
||||
- Create helper functions for multi-step operations
|
||||
- Use descriptive names that explain intent
|
||||
|
||||
Examples:
|
||||
|
||||
```javascript
|
||||
// ✅ Scannable: Intent is clear from variable names
|
||||
const isValidUser = user.isActive && user.hasPermissions;
|
||||
const shouldProcessPayment = amount > 0 && !order.isPaid;
|
||||
|
||||
// ✅ Scannable: Complex logic extracted to helper
|
||||
const searchParams = buildFreesoundSearchParams({ query, filters, pagination });
|
||||
const transformedResults = transformFreesoundResults({ rawResults });
|
||||
```
|
||||
|
||||
## Bad Unscannable Code
|
||||
|
||||
Avoid:
|
||||
|
||||
```javascript
|
||||
// ❌ Hard to scan: What does this condition mean?
|
||||
if (type === "effects" || !type) {
|
||||
params.append("filter", "duration:[* TO 30.0]");
|
||||
params.append("filter", `avg_rating:[${min_rating} TO *]`);
|
||||
if (commercial_only) {
|
||||
params.append("filter", 'license:("Attribution" OR "Creative Commons 0")');
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Hard to scan: Complex ternary
|
||||
const sortParam = query
|
||||
? sort === "score"
|
||||
? "score"
|
||||
: `${sort}_desc`
|
||||
: `${sort}_desc`;
|
||||
```
|
||||
|
||||
## Rule
|
||||
|
||||
Make code scannable by extracting intent into variables and helper functions. If you need to think about what code does, extract it. The reader should understand the flow without diving into implementation details.
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at our discord server.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
body:
|
||||
- type: input
|
||||
id: Platform
|
||||
attributes:
|
||||
label: Platform
|
||||
description: Please enter the platform on which you encountered the bug.
|
||||
placeholder: e.g. Windows 11, Ubuntu 14.04
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: Browser
|
||||
attributes:
|
||||
label: Browser
|
||||
description: Please enter the browser on which you encountered the bug.
|
||||
placeholder: e.g. Chrome 137, Firefox 137, Safari 17
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: current-behavior
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: A concise description of what you're experiencing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: recurrence-probability
|
||||
attributes:
|
||||
label: Recurrence Probability
|
||||
description: How often does this bug occur?
|
||||
options:
|
||||
- Always
|
||||
- Usually
|
||||
- Sometimes
|
||||
- Seldom
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
|
|
@ -1,344 +0,0 @@
|
|||
---
|
||||
applyTo: "**/*.{ts,tsx,js,jsx}"
|
||||
---
|
||||
|
||||
# Project Context
|
||||
|
||||
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
|
||||
|
||||
## Key Principles
|
||||
|
||||
- Zero configuration required
|
||||
- Subsecond performance
|
||||
- Maximum type safety
|
||||
- AI-friendly code generation
|
||||
|
||||
## Before Writing Code
|
||||
|
||||
1. Analyze existing patterns in the codebase
|
||||
2. Consider edge cases and error scenarios
|
||||
3. Follow the rules below strictly
|
||||
4. Validate accessibility requirements
|
||||
|
||||
## Rules
|
||||
|
||||
### Accessibility (a11y)
|
||||
|
||||
- Don't use `accessKey` attribute on any HTML element.
|
||||
- Don't set `aria-hidden="true"` on focusable elements.
|
||||
- Don't add ARIA roles, states, and properties to elements that don't support them.
|
||||
- Don't use distracting elements like `<marquee>` or `<blink>`.
|
||||
- Only use the `scope` prop on `<th>` elements.
|
||||
- Don't assign non-interactive ARIA roles to interactive HTML elements.
|
||||
- Make sure label elements have text content and are associated with an input.
|
||||
- Don't assign interactive ARIA roles to non-interactive HTML elements.
|
||||
- Don't assign `tabIndex` to non-interactive HTML elements.
|
||||
- Don't use positive integers for `tabIndex` property.
|
||||
- Don't include "image", "picture", or "photo" in img alt prop.
|
||||
- Don't use explicit role property that's the same as the implicit/default role.
|
||||
- Make static elements with click handlers use a valid role attribute.
|
||||
- Always include a `title` element for SVG elements.
|
||||
- Give all elements requiring alt text meaningful information for screen readers.
|
||||
- Make sure anchors have content that's accessible to screen readers.
|
||||
- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`.
|
||||
- Include all required ARIA attributes for elements with ARIA roles.
|
||||
- Make sure ARIA properties are valid for the element's supported roles.
|
||||
- Always include a `type` attribute for button elements.
|
||||
- Make elements with interactive roles and handlers focusable.
|
||||
- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`).
|
||||
- Always include a `lang` attribute on the html element.
|
||||
- Always include a `title` attribute for iframe elements.
|
||||
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
|
||||
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
|
||||
- Include caption tracks for audio and video elements.
|
||||
- Use semantic elements instead of role attributes in JSX.
|
||||
- Make sure all anchors are valid and navigable.
|
||||
- Ensure all ARIA properties (`aria-*`) are valid.
|
||||
- Use valid, non-abstract ARIA roles for elements with ARIA roles.
|
||||
- Use valid ARIA state and property values.
|
||||
- Use valid values for the `autocomplete` attribute on input elements.
|
||||
- Use correct ISO language/country codes for the `lang` attribute.
|
||||
|
||||
### Code Complexity and Quality
|
||||
|
||||
- Don't use consecutive spaces in regular expression literals.
|
||||
- Don't use the `arguments` object.
|
||||
- Don't use primitive type aliases or misleading types.
|
||||
- Don't use the comma operator.
|
||||
- Don't use empty type parameters in type aliases and interfaces.
|
||||
- Don't write functions that exceed a given Cognitive Complexity score.
|
||||
- Don't nest describe() blocks too deeply in test files.
|
||||
- Don't use unnecessary boolean casts.
|
||||
- Don't use unnecessary callbacks with flatMap.
|
||||
- Use for...of statements instead of Array.forEach.
|
||||
- Don't create classes that only have static members (like a static namespace).
|
||||
- Don't use this and super in static contexts.
|
||||
- Don't use unnecessary catch clauses.
|
||||
- Don't use unnecessary constructors.
|
||||
- Don't use unnecessary continue statements.
|
||||
- Don't export empty modules that don't change anything.
|
||||
- Don't use unnecessary escape sequences in regular expression literals.
|
||||
- Don't use unnecessary fragments.
|
||||
- Don't use unnecessary labels.
|
||||
- Don't use unnecessary nested block statements.
|
||||
- Don't rename imports, exports, and destructured assignments to the same name.
|
||||
- Don't use unnecessary string or template literal concatenation.
|
||||
- Don't use String.raw in template literals when there are no escape sequences.
|
||||
- Don't use useless case statements in switch statements.
|
||||
- Don't use ternary operators when simpler alternatives exist.
|
||||
- Don't use useless `this` aliasing.
|
||||
- Don't use any or unknown as type constraints.
|
||||
- Don't initialize variables to undefined.
|
||||
- Don't use the void operators (they're not familiar).
|
||||
- Use arrow functions instead of function expressions.
|
||||
- Use Date.now() to get milliseconds since the Unix Epoch.
|
||||
- Use .flatMap() instead of map().flat() when possible.
|
||||
- Use literal property access instead of computed property access.
|
||||
- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
|
||||
- Use concise optional chaining instead of chained logical expressions.
|
||||
- Use regular expression literals instead of the RegExp constructor when possible.
|
||||
- Don't use number literal object member names that aren't base 10 or use underscore separators.
|
||||
- Remove redundant terms from logical expressions.
|
||||
- Use while loops instead of for loops when you don't need initializer and update expressions.
|
||||
- Don't pass children as props.
|
||||
- Don't reassign const variables.
|
||||
- Don't use constant expressions in conditions.
|
||||
- Don't use `Math.min` and `Math.max` to clamp values when the result is constant.
|
||||
- Don't return a value from a constructor.
|
||||
- Don't use empty character classes in regular expression literals.
|
||||
- Don't use empty destructuring patterns.
|
||||
- Don't call global object properties as functions.
|
||||
- Don't declare functions and vars that are accessible outside their block.
|
||||
- Make sure builtins are correctly instantiated.
|
||||
- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.
|
||||
- Don't use variables and function parameters before they're declared.
|
||||
- Don't use 8 and 9 escape sequences in string literals.
|
||||
- Don't use literal numbers that lose precision.
|
||||
|
||||
### React and JSX Best Practices
|
||||
|
||||
- Don't use the return value of React.render.
|
||||
- Make sure all dependencies are correctly specified in React hooks.
|
||||
- Make sure all React hooks are called from the top level of component functions.
|
||||
- Don't forget key props in iterators and collection literals.
|
||||
- Don't destructure props inside JSX components in Solid projects.
|
||||
- Don't define React components inside other components.
|
||||
- Don't use event handlers on non-interactive elements.
|
||||
- Don't assign to React component props.
|
||||
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
|
||||
- Don't use dangerous JSX props.
|
||||
- Don't use Array index in keys.
|
||||
- Don't insert comments as text nodes.
|
||||
- Don't assign JSX properties multiple times.
|
||||
- Don't add extra closing tags for components without children.
|
||||
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
|
||||
- Watch out for possible "wrong" semicolons inside JSX elements.
|
||||
|
||||
### Correctness and Safety
|
||||
|
||||
- Don't assign a value to itself.
|
||||
- Don't return a value from a setter.
|
||||
- Don't compare expressions that modify string case with non-compliant values.
|
||||
- Don't use lexical declarations in switch clauses.
|
||||
- Don't use variables that haven't been declared in the document.
|
||||
- Don't write unreachable code.
|
||||
- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass.
|
||||
- Don't use control flow statements in finally blocks.
|
||||
- Don't use optional chaining where undefined values aren't allowed.
|
||||
- Don't have unused function parameters.
|
||||
- Don't have unused imports.
|
||||
- Don't have unused labels.
|
||||
- Don't have unused private class members.
|
||||
- Don't have unused variables.
|
||||
- Make sure void (self-closing) elements don't have children.
|
||||
- Don't return a value from a function with the return type 'void'
|
||||
- Use isNaN() when checking for NaN.
|
||||
- Make sure "for" loop update clauses move the counter in the right direction.
|
||||
- Make sure typeof expressions are compared to valid values.
|
||||
- Make sure generator functions contain yield.
|
||||
- Don't use await inside loops.
|
||||
- Don't use bitwise operators.
|
||||
- Don't use expressions where the operation doesn't change the value.
|
||||
- Make sure Promise-like statements are handled appropriately.
|
||||
- Don't use **dirname and **filename in the global scope.
|
||||
- Prevent import cycles.
|
||||
- Don't use configured elements.
|
||||
- Don't hardcode sensitive data like API keys and tokens.
|
||||
- Don't let variable declarations shadow variables from outer scopes.
|
||||
- Don't use the TypeScript directive @ts-ignore.
|
||||
- Prevent duplicate polyfills from Polyfill.io.
|
||||
- Don't use useless backreferences in regular expressions that always match empty strings.
|
||||
- Don't use unnecessary escapes in string literals.
|
||||
- Don't use useless undefined.
|
||||
- Make sure getters and setters for the same property are next to each other in class and object definitions.
|
||||
- Make sure object literals are declared consistently (defaults to explicit definitions).
|
||||
- Use static Response methods instead of new Response() constructor when possible.
|
||||
- Make sure switch-case statements are exhaustive.
|
||||
- Make sure the `preconnect` attribute is used when using Google Fonts.
|
||||
- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item.
|
||||
- Make sure iterable callbacks return consistent values.
|
||||
- Use `with { type: "json" }` for JSON module imports.
|
||||
- Use numeric separators in numeric literals.
|
||||
- Use object spread instead of `Object.assign()` when constructing new objects.
|
||||
- Always use the radix argument when using `parseInt()`.
|
||||
- Make sure JSDoc comment lines start with a single asterisk, except for the first one.
|
||||
- Include a description parameter for `Symbol()`.
|
||||
- Don't use spread (`...`) syntax on accumulators.
|
||||
- Don't use the `delete` operator.
|
||||
- Don't access namespace imports dynamically.
|
||||
- Don't use namespace imports.
|
||||
- Declare regex literals at the top level.
|
||||
- Don't use `target="_blank"` without `rel="noopener"`.
|
||||
|
||||
### TypeScript Best Practices
|
||||
|
||||
- Don't use TypeScript enums.
|
||||
- Don't export imported variables.
|
||||
- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
|
||||
- Don't use TypeScript namespaces.
|
||||
- Don't use non-null assertions with the `!` postfix operator.
|
||||
- Don't use parameter properties in class constructors.
|
||||
- Don't use user-defined types.
|
||||
- Use `as const` instead of literal types and type annotations.
|
||||
- Use either `T[]` or `Array<T>` consistently.
|
||||
- Initialize each enum member value explicitly.
|
||||
- Use `export type` for types.
|
||||
- Use `import type` for types.
|
||||
- Make sure all enum members are literal values.
|
||||
- Don't use TypeScript const enum.
|
||||
- Don't declare empty interfaces.
|
||||
- Don't let variables evolve into any type through reassignments.
|
||||
- Don't use the any type.
|
||||
- Don't misuse the non-null assertion operator (!) in TypeScript files.
|
||||
- Don't use implicit any type on variable declarations.
|
||||
- Don't merge interfaces and classes unsafely.
|
||||
- Don't use overload signatures that aren't next to each other.
|
||||
- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
|
||||
|
||||
### Style and Consistency
|
||||
|
||||
- Don't use global `eval()`.
|
||||
- Don't use callbacks in asynchronous tests and hooks.
|
||||
- Don't use negation in `if` statements that have `else` clauses.
|
||||
- Don't use nested ternary expressions.
|
||||
- Don't reassign function parameters.
|
||||
- This rule lets you specify global variable names you don't want to use in your application.
|
||||
- Don't use specified modules when loaded by import or require.
|
||||
- Don't use constants whose value is the upper-case version of their name.
|
||||
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
|
||||
- Don't use template literals if you don't need interpolation or special-character handling.
|
||||
- Don't use `else` blocks when the `if` block breaks early.
|
||||
- Don't use yoda expressions.
|
||||
- Don't use Array constructors.
|
||||
- Use `at()` instead of integer index access.
|
||||
- Follow curly brace conventions.
|
||||
- Use `else if` instead of nested `if` statements in `else` clauses.
|
||||
- Use single `if` statements instead of nested `if` clauses.
|
||||
- Use `new` for all builtins except `String`, `Number`, and `Boolean`.
|
||||
- Use consistent accessibility modifiers on class properties and methods.
|
||||
- Use `const` declarations for variables that are only assigned once.
|
||||
- Put default function parameters and optional function parameters last.
|
||||
- Include a `default` clause in switch statements.
|
||||
- Use the `**` operator instead of `Math.pow`.
|
||||
- Use `for-of` loops when you need the index to extract an item from the iterated array.
|
||||
- Use `node:assert/strict` over `node:assert`.
|
||||
- Use the `node:` protocol for Node.js builtin modules.
|
||||
- Use Number properties instead of global ones.
|
||||
- Use assignment operator shorthand where possible.
|
||||
- Use function types instead of object types with call signatures.
|
||||
- Use template literals over string concatenation.
|
||||
- Use `new` when throwing an error.
|
||||
- Don't throw non-Error values.
|
||||
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
|
||||
- Use standard constants instead of approximated literals.
|
||||
- Don't assign values in expressions.
|
||||
- Don't use async functions as Promise executors.
|
||||
- Don't reassign exceptions in catch clauses.
|
||||
- Don't reassign class members.
|
||||
- Don't compare against -0.
|
||||
- Don't use labeled statements that aren't loops.
|
||||
- Don't use void type outside of generic or return types.
|
||||
- Don't use console.
|
||||
- Don't use control characters and escape sequences that match control characters in regular expression literals.
|
||||
- Don't use debugger.
|
||||
- Don't assign directly to document.cookie.
|
||||
- Use `===` and `!==`.
|
||||
- Don't use duplicate case labels.
|
||||
- Don't use duplicate class members.
|
||||
- Don't use duplicate conditions in if-else-if chains.
|
||||
- Don't use two keys with the same name inside objects.
|
||||
- Don't use duplicate function parameter names.
|
||||
- Don't have duplicate hooks in describe blocks.
|
||||
- Don't use empty block statements and static blocks.
|
||||
- Don't let switch clauses fall through.
|
||||
- Don't reassign function declarations.
|
||||
- Don't allow assignments to native objects and read-only global variables.
|
||||
- Use Number.isFinite instead of global isFinite.
|
||||
- Use Number.isNaN instead of global isNaN.
|
||||
- Don't assign to imported bindings.
|
||||
- Don't use irregular whitespace characters.
|
||||
- Don't use labels that share a name with a variable.
|
||||
- Don't use characters made with multiple code points in character class syntax.
|
||||
- Make sure to use new and constructor properly.
|
||||
- Don't use shorthand assign when the variable appears on both sides.
|
||||
- Don't use octal escape sequences in string literals.
|
||||
- Don't use Object.prototype builtins directly.
|
||||
- Don't redeclare variables, functions, classes, and types in the same scope.
|
||||
- Don't have redundant "use strict".
|
||||
- Don't compare things where both sides are exactly the same.
|
||||
- Don't let identifiers shadow restricted names.
|
||||
- Don't use sparse arrays (arrays with holes).
|
||||
- Don't use template literal placeholder syntax in regular strings.
|
||||
- Don't use the then property.
|
||||
- Don't use unsafe negation.
|
||||
- Don't use var.
|
||||
- Don't use with statements in non-strict contexts.
|
||||
- Make sure async functions actually use await.
|
||||
- Make sure default clauses in switch statements come last.
|
||||
- Make sure to pass a message value when creating a built-in error.
|
||||
- Make sure get methods always return a value.
|
||||
- Use a recommended display strategy with Google Fonts.
|
||||
- Make sure for-in loops include an if statement.
|
||||
- Use Array.isArray() instead of instanceof Array.
|
||||
- Make sure to use the digits argument with Number#toFixed().
|
||||
- Make sure to use the "use strict" directive in script files.
|
||||
|
||||
### Next.js Specific Rules
|
||||
|
||||
- Don't use `<img>` elements in Next.js projects.
|
||||
- Don't use `<head>` elements in Next.js projects.
|
||||
- Don't import next/document outside of pages/\_document.jsx in Next.js projects.
|
||||
- Don't use the next/head module in pages/\_document.js on Next.js projects.
|
||||
|
||||
### Testing Best Practices
|
||||
|
||||
- Don't use export or module.exports in test files.
|
||||
- Don't use focused tests.
|
||||
- Make sure the assertion function, like expect, is placed inside an it() function call.
|
||||
- Don't use disabled tests.
|
||||
|
||||
## Common Tasks
|
||||
|
||||
- `npx ultracite init` - Initialize Ultracite in your project
|
||||
- `npx ultracite format` - Format and fix code automatically
|
||||
- `npx ultracite lint` - Check for issues without fixing
|
||||
|
||||
## Example: Error Handling
|
||||
|
||||
```typescript
|
||||
// ✅ Good: Comprehensive error handling
|
||||
try {
|
||||
const result = await fetchData();
|
||||
return { success: true, data: result };
|
||||
} catch (error) {
|
||||
console.error("API call failed:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
|
||||
// ❌ Bad: Swallowing errors
|
||||
try {
|
||||
return await fetchData();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
```
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
⚠️ READ BEFORE SUBMITTING ⚠️
|
||||
|
||||
We are not currently accepting PRs except for critical bugs.
|
||||
|
||||
If this is a bug fix:
|
||||
- [ ] I've opened an issue first
|
||||
- [ ] This was approved by a maintainer
|
||||
|
||||
If this is a feature:
|
||||
|
||||
This PR will be closed. Please open an issue to discuss first.
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# asdf version management
|
||||
.tool-versions
|
||||
|
||||
node_modules
|
||||
.cursorignore
|
||||
.turbo
|
||||
|
||||
.env*
|
||||
!*.env.example
|
||||
|
||||
# cursor
|
||||
bun.lockb
|
||||
|
||||
# Twiggy
|
||||
.cursor/rules/file-structure.mdc
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"emmet.showExpandedAbbreviation": "never",
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
}
|
||||
342
AGENTS.md
342
AGENTS.md
|
|
@ -1,342 +0,0 @@
|
|||
# AGENTS.md
|
||||
|
||||
## Overview
|
||||
|
||||
Privacy-first video editor, with a focus on simplicity and ease of use.
|
||||
|
||||
## Lib vs Utils
|
||||
|
||||
- `lib/` - domain logic (specific to this app)
|
||||
- `utils/` - small helper utils (generic, could be copy-pasted into any other app)
|
||||
|
||||
## Core Editor System
|
||||
|
||||
The editor uses a **singleton EditorCore** that manages all editor state through specialized managers.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
EditorCore (singleton)
|
||||
├── playback: PlaybackManager
|
||||
├── timeline: TimelineManager
|
||||
├── scene: SceneManager
|
||||
├── project: ProjectManager
|
||||
├── media: MediaManager
|
||||
└── renderer: RendererManager
|
||||
```
|
||||
|
||||
### When to Use What
|
||||
|
||||
#### In React Components
|
||||
|
||||
**Always use the `useEditor()` hook:**
|
||||
|
||||
```typescript
|
||||
import { useEditor } from '@/hooks/use-editor';
|
||||
|
||||
function MyComponent() {
|
||||
const editor = useEditor();
|
||||
const tracks = editor.timeline.getTracks();
|
||||
|
||||
// Call methods
|
||||
editor.timeline.addTrack({ type: 'media' });
|
||||
|
||||
// Display data (auto re-renders on changes)
|
||||
return <div>{tracks.length} tracks</div>;
|
||||
}
|
||||
```
|
||||
|
||||
The hook:
|
||||
|
||||
- Returns the singleton instance
|
||||
- Subscribes to all manager changes
|
||||
- Automatically re-renders when state changes
|
||||
|
||||
#### Outside React Components
|
||||
|
||||
**Use `EditorCore.getInstance()` directly:**
|
||||
|
||||
```typescript
|
||||
// In utilities, event handlers, or non-React code
|
||||
import { EditorCore } from "@/core";
|
||||
|
||||
const editor = EditorCore.getInstance();
|
||||
await editor.export({ format: "mp4", quality: "high" });
|
||||
```
|
||||
|
||||
## Actions System
|
||||
|
||||
Actions are the trigger layer for user-initiated operations. The single source of truth is `@/lib/actions/definitions.ts`.
|
||||
|
||||
**To add a new action:**
|
||||
|
||||
1. Add it to `ACTIONS` in `@/lib/actions/definitions.ts`:
|
||||
|
||||
```typescript
|
||||
export const ACTIONS = {
|
||||
"my-action": {
|
||||
description: "What the action does",
|
||||
category: "editing",
|
||||
defaultShortcuts: ["ctrl+m"],
|
||||
},
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
2. Add handler in `@/hooks/use-editor-actions.ts`:
|
||||
|
||||
```typescript
|
||||
useActionHandler(
|
||||
"my-action",
|
||||
() => {
|
||||
// implementation
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
```
|
||||
|
||||
**In components, use `invokeAction()` for user-triggered operations:**
|
||||
|
||||
```typescript
|
||||
import { invokeAction } from '@/lib/actions';
|
||||
|
||||
// Good - uses action system
|
||||
const handleSplit = () => invokeAction("split-selected");
|
||||
|
||||
// Avoid - bypasses UX layer (toasts, validation feedback)
|
||||
const handleSplit = () => editor.timeline.splitElements({ ... });
|
||||
```
|
||||
|
||||
Direct `editor.xxx()` calls are for internal use (commands, tests, complex multi-step operations).
|
||||
|
||||
## Commands System
|
||||
|
||||
Commands handle undo/redo. They live in `@/lib/commands/` organized by domain (timeline, media, scene).
|
||||
|
||||
Each command extends `Command` from `@/lib/commands/base-command` and implements:
|
||||
|
||||
- `execute()` - saves current state, then does the mutation
|
||||
- `undo()` - restores the saved state
|
||||
|
||||
Actions and commands work together: actions are "what triggered this", commands are "how to do it (and undo it)".
|
||||
|
||||
# Frontend Agent Guide (React + Clean UI)
|
||||
|
||||
This repository uses **test-first development** and **clean UI/code** principles.
|
||||
AI agents must follow these rules when planning, coding, and refactoring.
|
||||
|
||||
---
|
||||
|
||||
## 1) Core Principles
|
||||
|
||||
### Test-first is mandatory
|
||||
|
||||
Follow **Red → Green → Refactor** where practical:
|
||||
|
||||
1. **Red:** Write a failing test that captures the behavior.
|
||||
2. **Green:** Implement the simplest code to pass.
|
||||
3. **Refactor:** Improve structure/readability with tests green.
|
||||
|
||||
UI work that is purely presentational may start with a minimal component and then tests, but behavior changes must be test-first.
|
||||
|
||||
### Clean code and clean UI always
|
||||
|
||||
- Prefer **clarity over cleverness**.
|
||||
- Use **small components**, **meaningful names**, **single responsibility**.
|
||||
- Keep UI structure simple; avoid deeply nested layouts.
|
||||
- Avoid over-engineering; introduce abstractions only when pressure appears.
|
||||
|
||||
### Minimal scope
|
||||
|
||||
- Implement only what’s needed for the current task/tests.
|
||||
- No premature “future-proofing.”
|
||||
|
||||
### Never use `any`
|
||||
|
||||
- **Do not use `any` as a type.**
|
||||
- Prefer precise types or generics.
|
||||
|
||||
---
|
||||
|
||||
## 2) Workflow Rules (How the agent works)
|
||||
|
||||
### Before coding
|
||||
|
||||
- Identify the smallest behavior to add.
|
||||
- Decide where it belongs (component, hook, util, store).
|
||||
- Write/adjust tests first for behavior changes.
|
||||
|
||||
### While coding
|
||||
|
||||
- Keep changes small and incremental.
|
||||
- Run tests frequently and keep them green.
|
||||
|
||||
### After coding
|
||||
|
||||
- Refactor for readability and structure.
|
||||
- Ensure linting/formatting pass.
|
||||
- Update docs only if behavior/usage changed.
|
||||
|
||||
---
|
||||
|
||||
## 3) Project Architecture Expectations (React)
|
||||
|
||||
### Component structure
|
||||
|
||||
- **UI components**: presentational, no side effects.
|
||||
- **Feature components**: orchestrate UI + hooks/state.
|
||||
- **Hooks**: encapsulate shared logic.
|
||||
- **Utilities**: pure functions.
|
||||
|
||||
### Dependency direction
|
||||
|
||||
- Features can use shared UI/components/utils.
|
||||
- Shared UI must not depend on feature-specific logic.
|
||||
|
||||
### State
|
||||
|
||||
- Prefer local state where possible.
|
||||
- Use a shared store only when state must be shared across major areas.
|
||||
|
||||
---
|
||||
|
||||
## 4) Testing Strategy (React)
|
||||
|
||||
### Preferred test types
|
||||
|
||||
1. **Unit tests**: pure utils, hooks (default).
|
||||
2. **Component tests**: React Testing Library for behavior.
|
||||
3. **E2E tests**: only for high-level flows.
|
||||
|
||||
### What to test
|
||||
|
||||
- Behavior and user interactions, not implementation details.
|
||||
- Edge cases and failure paths.
|
||||
|
||||
### Testing rules
|
||||
|
||||
- Every new behavior must include tests.
|
||||
- Tests must be deterministic.
|
||||
- Avoid snapshot tests for logic-heavy components.
|
||||
|
||||
---
|
||||
|
||||
## 5) Clean Code Rules (Non-negotiable)
|
||||
|
||||
### Naming
|
||||
|
||||
- Use intention-revealing names.
|
||||
- Avoid ambiguous abbreviations.
|
||||
|
||||
### Functions & components
|
||||
|
||||
- Keep functions small.
|
||||
- Keep components focused.
|
||||
- Avoid prop drilling if a context or store is more appropriate.
|
||||
|
||||
### Error handling
|
||||
|
||||
- Fail fast; show user-friendly messages where needed.
|
||||
- No silent failures.
|
||||
|
||||
### Comments
|
||||
|
||||
- Use comments sparingly, only for _why_, not _what_.
|
||||
|
||||
### Formatting
|
||||
|
||||
- Follow repo lint/format rules.
|
||||
- No unused variables or dead code.
|
||||
|
||||
---
|
||||
|
||||
## 6) React Conventions
|
||||
|
||||
### Props & types
|
||||
|
||||
- Use typed props and shared types in `types/` or local `types.ts`.
|
||||
- Avoid widening types; keep them as narrow as possible.
|
||||
|
||||
### Accessibility
|
||||
|
||||
- Use semantic HTML.
|
||||
- Ensure keyboard navigability for interactive elements.
|
||||
|
||||
### Styling
|
||||
|
||||
- Follow repo styling approach consistently.
|
||||
- Keep styles co-located with components when possible.
|
||||
|
||||
---
|
||||
|
||||
## 7) Implementation Guidelines
|
||||
|
||||
### When adding new UI behavior
|
||||
|
||||
1. Write a test describing the interaction.
|
||||
2. Implement the smallest change to pass.
|
||||
3. Refactor for clarity and reusability.
|
||||
|
||||
### When fixing a bug
|
||||
|
||||
1. Add a failing test.
|
||||
2. Fix with minimal change.
|
||||
3. Refactor if needed.
|
||||
|
||||
---
|
||||
|
||||
## 8) Agent Output Expectations
|
||||
|
||||
When producing changes, the agent should:
|
||||
|
||||
- Include tests with each behavior change.
|
||||
- Keep patches focused and minimal.
|
||||
- Summarize what changed and why.
|
||||
|
||||
---
|
||||
|
||||
## 9) Quality Gate Checklist (must pass)
|
||||
|
||||
- [ ] All tests pass (`unit`, `component`, `e2e` as applicable)
|
||||
- [ ] Lint/format checks pass
|
||||
- [ ] No flaky or time-dependent tests
|
||||
- [ ] No new unused exports or dead code
|
||||
- [ ] Accessibility reviewed for interactive changes
|
||||
|
||||
---
|
||||
|
||||
## 10) Default Commands (adjust if repo differs)
|
||||
|
||||
Prefer these commands if they exist:
|
||||
|
||||
- `npm test` / `pnpm test`
|
||||
- `npm run test:watch`
|
||||
- `npm run test:e2e`
|
||||
- `npm run lint`
|
||||
- `npm run format`
|
||||
|
||||
Follow `package.json` scripts if different.
|
||||
|
||||
---
|
||||
|
||||
## 11) Anti-Patterns to Avoid
|
||||
|
||||
- Writing behavior before tests (unless purely presentational).
|
||||
- Mixing unrelated refactors with feature work.
|
||||
- Over-mocking your own code.
|
||||
- Snapshot tests for logic-heavy UI.
|
||||
- Large shared “utils” dumping grounds.
|
||||
|
||||
---
|
||||
|
||||
## 12) When uncertain
|
||||
|
||||
- Choose the simplest interpretation.
|
||||
- Write tests that document the behavior.
|
||||
- Keep changes minimal and reversible.
|
||||
|
||||
---
|
||||
|
||||
## 13) Commit message
|
||||
|
||||
- Follow commit message convention standard
|
||||
8
TODO.md
8
TODO.md
|
|
@ -1,8 +0,0 @@
|
|||
1. Clean up repo, remove home page
|
||||
2. Adding storyboard? Test coverage
|
||||
3. Add up auth page and auth screen
|
||||
4. Connect cloud sync including project and assets
|
||||
5. Adding script writer feature
|
||||
6. Adding storyboarder
|
||||
7. Adding voiceover
|
||||
8. Adding editor
|
||||
Binary file not shown.
|
|
@ -1,3 +1,5 @@
|
|||
node_modules/
|
||||
|
||||
# Turborepo
|
||||
.turbo
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
legacy-peer-deps=true
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Config } from "drizzle-kit";
|
||||
import * as dotenv from "dotenv";
|
||||
import { webEnv } from "@vibecut/env/web";
|
||||
import { webEnv } from "./src/env/web";
|
||||
|
||||
// Load the right env file based on environment
|
||||
if (webEnv.NODE_ENV === "production") {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -33,8 +33,6 @@
|
|||
"@hugeicons/core-free-icons": "^3.1.1",
|
||||
"@hugeicons/react": "^1.1.4",
|
||||
"@huggingface/transformers": "^3.8.1",
|
||||
"@vibecut/env": "workspace:*",
|
||||
"@vibecut/ui": "workspace:*",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { webEnv } from "@vibecut/env/web";
|
||||
import { webEnv } from "@/env/web";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import { Toaster } from "../components/ui/sonner";
|
|||
import { TooltipProvider } from "../components/ui/tooltip";
|
||||
import { baseMetaData } from "./metadata";
|
||||
import { BotIdClient } from "botid/client";
|
||||
import { webEnv } from "@vibecut/env/web";
|
||||
import { Inter } from "next/font/google";
|
||||
import { AuthProvider } from "@/components/providers/auth-provider";
|
||||
import { AuthGuard } from "@/components/auth/auth-guard";
|
||||
import { webEnv } from "@/env/web";
|
||||
|
||||
const siteFont = Inter({ subsets: ["latin"] });
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ import {
|
|||
ArrowDown02Icon,
|
||||
InformationCircleIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { OcVideoIcon } from "@vibecut/ui/icons";
|
||||
import { OcVideoIcon } from "@/ui/icons";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import { backendAuthApi } from "@/lib/auth/api-client";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { OcDataBuddyIcon, OcMarbleIcon, } from "@vibecut/ui/icons";
|
||||
import { OcDataBuddyIcon, OcMarbleIcon } from "@/ui/icons";
|
||||
|
||||
export const SITE_URL = "https://vibecut.app";
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
TextIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { OcVideoIcon } from "@vibecut/ui/icons";
|
||||
import { OcVideoIcon } from "@/ui/icons";
|
||||
|
||||
export const TRACK_COLORS: Record<TrackType, { background: string }> = {
|
||||
video: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const webEnvSchema = z.object({
|
||||
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
||||
DATABASE_URL: z.string().min(1),
|
||||
UPSTASH_REDIS_REST_URL: z.string().min(1),
|
||||
UPSTASH_REDIS_REST_TOKEN: z.string().min(1),
|
||||
FREESOUND_API_KEY: z.string().min(1),
|
||||
FREESOUND_CLIENT_ID: z.string().min(1).optional(),
|
||||
MARBLE_WORKSPACE_KEY: z.string().min(1).optional(),
|
||||
MODAL_TRANSCRIPTION_URL: z.string().min(1).optional(),
|
||||
NEXT_PUBLIC_AUTH_API_BASE_URL: z.string().min(1).optional(),
|
||||
});
|
||||
|
||||
export const webEnv = webEnvSchema.parse({
|
||||
...process.env,
|
||||
NODE_ENV: process.env.NODE_ENV ?? "development",
|
||||
});
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "./schema";
|
||||
import { webEnv } from "@vibecut/env/web";
|
||||
import { webEnv } from "@/env/web";
|
||||
|
||||
let _db: ReturnType<typeof drizzle> | null = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
import { Redis } from "@upstash/redis";
|
||||
import { webEnv } from "@vibecut/env/web";
|
||||
import { webEnv } from "@/env/web";
|
||||
|
||||
const redis = new Redis({
|
||||
url: webEnv.UPSTASH_REDIS_REST_URL,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import type { SVGProps } from "react";
|
||||
import { ChartArea, Gem, Video } from "lucide-react";
|
||||
|
||||
type IconProps = SVGProps<SVGSVGElement>;
|
||||
|
||||
export function OcVideoIcon(props: IconProps) {
|
||||
return <Video {...props} />;
|
||||
}
|
||||
|
||||
export function OcMarbleIcon(props: IconProps) {
|
||||
return <Gem {...props} />;
|
||||
}
|
||||
|
||||
export function OcDataBuddyIcon(props: IconProps) {
|
||||
return <ChartArea {...props} />;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
45
biome.json
45
biome.json
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/.next/**",
|
||||
"!**/node_modules/**",
|
||||
"!**/dist/**",
|
||||
"!**/build/**",
|
||||
"!**/apps/web/public/ffmpeg/**",
|
||||
"!**/apps/web/src/components/ui/**"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"lineWidth": 80
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": true,
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
package.json
37
package.json
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"name": "vibecut",
|
||||
"packageManager": "bun@1.2.18",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev:web": "turbo run dev --filter=@vibecut/web",
|
||||
"build:web": "turbo run build --filter=@vibecut/web",
|
||||
"dev:storybook": "bun run --cwd apps/web storybook",
|
||||
"build:storybook": "bun run --cwd apps/web build:storybook",
|
||||
"dev:tools": "turbo run dev --filter=@vibecut/tools",
|
||||
"build:tools": "turbo run build --filter=@vibecut/tools",
|
||||
"start:tools": "turbo run start --filter=@vibecut/tools",
|
||||
"lint:web": "biome lint apps/web/src --max-diagnostics=1000",
|
||||
"lint:web:fix": "biome lint apps/web/src --write --max-diagnostics=1000",
|
||||
"format:web": "biome format apps/web/src/services/renderer --write --max-diagnostics=1000",
|
||||
"test": "bun test",
|
||||
"test:web": "turbo run test --filter=@vibecut/web",
|
||||
"test:web:coverage": "turbo run test:coverage --filter=@vibecut/web",
|
||||
"test:e2e:web": "bun run --cwd apps/web test:e2e",
|
||||
"test:e2e:web:ui": "bun run --cwd apps/web test:e2e:ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"next": "^16.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2.7.5",
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@tailwindcss/oxide"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"name": "@vibecut/env",
|
||||
"version": "0.0.0",
|
||||
"description": "Environment package for VibeCut",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./web": "./src/web.ts",
|
||||
"./tools": "./src/tools.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "^16.4.7",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^24.2.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const toolsEnvSchema = z.object({
|
||||
// Node
|
||||
NODE_ENV: z.enum(["development", "production", "test"]),
|
||||
ANALYZE: z.string().optional(),
|
||||
NEXT_RUNTIME: z.enum(["nodejs", "edge"]).optional(),
|
||||
|
||||
// Public
|
||||
NEXT_PUBLIC_SITE_URL: z.url().default("http://localhost:3000"),
|
||||
|
||||
// Server
|
||||
DATABASE_URL: z
|
||||
.string()
|
||||
.startsWith("postgres://")
|
||||
.or(z.string().startsWith("postgresql://")),
|
||||
|
||||
UPSTASH_REDIS_REST_URL: z.url(),
|
||||
UPSTASH_REDIS_REST_TOKEN: z.string(),
|
||||
});
|
||||
|
||||
export type ToolsEnv = z.infer<typeof toolsEnvSchema>;
|
||||
|
||||
export const toolsEnv = toolsEnvSchema.parse(process.env);
|
||||
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="node" />
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
const webEnvSchema = z.object({
|
||||
// Node
|
||||
NODE_ENV: z.enum(["development", "production", "test"]),
|
||||
ANALYZE: z.string().optional(),
|
||||
NEXT_RUNTIME: z.enum(["nodejs", "edge"]).optional(),
|
||||
|
||||
// Public
|
||||
NEXT_PUBLIC_SITE_URL: z.url().default("http://localhost:3000"),
|
||||
NEXT_PUBLIC_MARBLE_API_URL: z.url(),
|
||||
|
||||
// Server
|
||||
DATABASE_URL: z
|
||||
.string()
|
||||
.startsWith("postgres://")
|
||||
.or(z.string().startsWith("postgresql://")),
|
||||
|
||||
UPSTASH_REDIS_REST_URL: z.url(),
|
||||
UPSTASH_REDIS_REST_TOKEN: z.string(),
|
||||
MARBLE_WORKSPACE_KEY: z.string(),
|
||||
FREESOUND_CLIENT_ID: z.string(),
|
||||
FREESOUND_API_KEY: z.string(),
|
||||
MODAL_TRANSCRIPTION_URL: z.url(),
|
||||
});
|
||||
|
||||
export type WebEnv = z.infer<typeof webEnvSchema>;
|
||||
|
||||
export const webEnv = webEnvSchema.parse(process.env);
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "@vibecut/ui",
|
||||
"version": "0.0.0",
|
||||
"description": "UI package for VibeCut",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
"./icons": "./src/icons/index.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/react": "^6.0.2",
|
||||
"@types/react": "^19.2.7",
|
||||
"react": "^19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
export function OcVercelIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="20"
|
||||
height="18"
|
||||
viewBox="0 0 76 65"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Vercel</title>
|
||||
<path d="M37.5274 0L75.0548 65H0L37.5274 0Z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function OcMarbleIcon({
|
||||
className = "",
|
||||
size = 32,
|
||||
}: {
|
||||
className?: string;
|
||||
size?: number;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 256 256"
|
||||
>
|
||||
<title>Marble</title>
|
||||
<path fill="#202027" d="M0 0h256v256H0z" />
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M116.032 94.016q1.408.256 6.272 9.856 4.856 9.472 11.904 24.576l4.096 8.576a42.4 42.4 0 0 0 4.992-6.784q2.304-3.96 5.76-10.624 3.96-7.56 5.504-9.728 6.144-9.344 13.312-19.84.896-1.408 3.2-1.92a4.8 4.8 0 0 1 1.28-.128q3.072 0 6.784 2.56 3.84 2.56 4.992 5.248a4.8 4.8 0 0 1 .256 1.92q0 1.28-.128 1.92l-.64 2.944q-.256 1.664-.512 3.968a3200 3200 0 0 0-5.888 46.848 56 56 0 0 0-.512 8.064l-.256 4.096a32 32 0 0 0-.128 2.944q0 1.672-.384 2.304-.384.64-1.408.64-.768 0-2.304-.384-4.096-.896-6.272-3.2-2.176-2.432-2.432-6.656-.128-2.176-.128-6.784 0-4.736.256-14.464l.128-7.04.256-10.112q-1.152 1.024-5.888 8.064a328 328 0 0 0-9.088 14.464q-4.48 7.56-5.888 11.648-1.28 3.2-2.56 4.736-1.152 1.536-2.816 1.536-1.536 0-3.968-1.408-6.912-4.224-8.448-11.648a336 336 0 0 0-6.016-23.68 40 40 0 0 0-2.56-6.272q-1.792-3.968-2.304-4.992l-1.664-3.328q-18.68 24.832-25.344 45.568-1.28 4.352-2.432 6.272-1.152 1.792-2.688 1.792-2.432 0-6.912-4.352Q72 158.144 72 154.304q0-2.04.768-4.096a108 108 0 0 1 16-30.08l3.968-5.248a432 432 0 0 0 12.16-16.64q3.072-4.48 8.192-4.48.896 0 2.944.256"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function OcDataBuddyIcon({
|
||||
className = "",
|
||||
size = 32,
|
||||
}: {
|
||||
className?: string;
|
||||
size?: number;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
viewBox="0 0 8 8"
|
||||
shapeRendering="crispEdges"
|
||||
>
|
||||
<title>Data Buddy</title>
|
||||
<path d="M0 0h8v8H0z" />
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M1 1h1v6H1zm1 0h4v1H2zm4 1h1v1H6zm0 1h1v1H6zm0 1h1v1H6zm0 1h1v1H6zM2 6h4v1H2zm1-3h1v1H3zm1 1h1v1H4z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./brand";
|
||||
export * from "./ui";
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
export function OcVideoIcon({
|
||||
className = "",
|
||||
size = 32,
|
||||
}: {
|
||||
className?: string;
|
||||
size?: number;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
className={className}
|
||||
viewBox="0 0 29 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Video</title>
|
||||
<path
|
||||
d="M2.41667 11C2.41667 7.70017 2.41667 6.05025 3.65537 5.02513C4.89405 4 6.88771 4 10.875 4H12.0833C16.0706 4 18.0642 4 19.303 5.02513C20.5417 6.05025 20.5417 7.70017 20.5417 11V13C20.5417 16.2998 20.5417 17.9497 19.303 18.9749C18.0642 20 16.0706 20 12.0833 20H10.875C6.88771 20 4.89405 20 3.65537 18.9749C2.41667 17.9497 2.41667 16.2998 2.41667 13V11Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M20.5417 8.90585L20.6938 8.80196C23.2504 7.05623 24.5287 6.18336 25.556 6.60482C26.5833 7.02628 26.5833 8.42355 26.5833 11.2181V12.7819C26.5833 15.5765 26.5833 16.9737 25.556 17.3952C24.5287 17.8166 23.2504 16.9438 20.6938 15.198L20.5417 15.0941"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# Clean Up Repo and Remove Home Page
|
||||
|
||||
## Goal
|
||||
Reduce surface area for the initial release by removing the public marketing/home page and keeping only product-critical routes.
|
||||
|
||||
## User Stories
|
||||
- As a product owner, I want only core editor routes enabled so maintenance burden is lower.
|
||||
- As a developer, I want a simplified app structure so new features are easier to ship.
|
||||
- As a user, I want to land directly on the app flow instead of a marketing page
|
||||
|
||||
## Scope
|
||||
- Remove or deprecate current home page route (`/`).
|
||||
- Redirect `/` to the primary product entry point (`/projects`).
|
||||
- Remove non-product marketing/content routes from app runtime:
|
||||
- `/blog`
|
||||
- `/blog/[slug]`
|
||||
- `/contributors`
|
||||
- `/roadmap`
|
||||
- `/sponsors`
|
||||
- `/rss.xml`
|
||||
- Remove unused landing-page components/assets tied only to home page.
|
||||
- Update header/footer links to avoid removed pages.
|
||||
- Update sitemap entries to include only active product/legal routes.
|
||||
|
||||
## Technical Plan
|
||||
1. Identify root route implementation under `apps/web/src/app/page.tsx` (or equivalent).
|
||||
2. Replace with server redirect using Next.js `redirect()`.
|
||||
3. Remove non-product routes and related content modules.
|
||||
4. Remove dead UI blocks/components no longer referenced.
|
||||
5. Update header/footer links that still point to removed sections.
|
||||
6. Update sitemap and route metadata to avoid deleted pages.
|
||||
7. Run lint/build and smoke test key routes.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Visiting `/` redirects to chosen product entry route.
|
||||
- Visiting removed marketing routes returns `404`.
|
||||
- No broken imports from removed home-page files.
|
||||
- No dead links in top-level navigation.
|
||||
- Build succeeds without route-level errors.
|
||||
|
||||
## Risks and Mitigations
|
||||
- Risk: Breaking SEO or legal content discoverability.
|
||||
- Mitigation: Keep explicit routes for `/privacy`, `/terms`, etc.
|
||||
|
||||
## Deliverables
|
||||
- PR removing home page and related dead files.
|
||||
- Short migration note in README/changelog.
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
# Storybook and Test Coverage
|
||||
|
||||
## Goal
|
||||
Introduce stable quality gates in phases: lock in repeatable tests first, then add Storybook for UI review.
|
||||
|
||||
## Why This Order
|
||||
- Tests provide immediate regression protection for core editor behavior.
|
||||
- Storybook is valuable, but should not delay foundational reliability.
|
||||
- Existing migration tests already exist and can be expanded instead of starting from zero.
|
||||
|
||||
## User Stories
|
||||
- As a maintainer, I want deterministic tests for critical editor logic so refactors are safer.
|
||||
- As a lead, I want CI coverage artifacts and baseline thresholds to monitor progress.
|
||||
- As a designer/developer, I want isolated stories for shared UI components.
|
||||
|
||||
## Scope
|
||||
### Phase 1 (implement now)
|
||||
- Standardize `bun test` scripts for `apps/web` and root workspace.
|
||||
- Enable coverage reports (`text` + `lcov`) and CI artifact upload.
|
||||
- Add high-value domain tests for timeline/time helpers and storage migrations.
|
||||
|
||||
### Phase 2
|
||||
- Add Storybook to `apps/web` targeting reusable shared components first.
|
||||
- Add at least 10 focused stories (button/input/dialog/shell components).
|
||||
|
||||
### Phase 3
|
||||
- Introduce coverage threshold policy and ratchet strategy.
|
||||
- Add guardrail: coverage must not decrease on PRs.
|
||||
|
||||
## Non-Goals (This Initiative)
|
||||
- Full E2E test suite for rendering/export pipelines.
|
||||
- Browser automation for editor interactions.
|
||||
- Large auth/backend integration testing.
|
||||
|
||||
## Technical Plan
|
||||
1. Add explicit scripts:
|
||||
- `apps/web`: `test`, `test:coverage`
|
||||
- root: `test:web`, `test:web:coverage`
|
||||
2. Update CI:
|
||||
- run tests in `apps/web`
|
||||
- generate coverage files
|
||||
- upload coverage artifact
|
||||
3. Expand unit coverage in pure modules:
|
||||
- timecode parsing/formatting/snap behavior
|
||||
- timeline bookmark helper behavior
|
||||
- existing storage migration tests remain mandatory
|
||||
4. Storybook setup in a separate implementation PR after test baseline is stable.
|
||||
|
||||
## Acceptance Criteria
|
||||
- `npm run test:web` executes and passes locally.
|
||||
- `npm run test:web:coverage` generates `apps/web/coverage/lcov.info`.
|
||||
- CI uploads coverage artifacts for inspection.
|
||||
- New tests cover critical branches in time/timeline helpers.
|
||||
- Storybook remains planned, but does not block Phase 1 rollout.
|
||||
|
||||
## Risks and Mitigations
|
||||
- Risk: Bun/Next/tooling mismatch in CI.
|
||||
- Mitigation: Keep to Bun-native test runner and simple scripts.
|
||||
- Risk: Initial threshold too strict causing noisy failures.
|
||||
- Mitigation: start with baseline visibility + no-regression policy, then ratchet.
|
||||
|
||||
## Deliverables
|
||||
- Updated scripts and CI workflow for tests + coverage.
|
||||
- Added domain tests under `apps/web/src`.
|
||||
- Updated planning document with phased rollout.
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
# Auth Pages and Backend Auth Flow
|
||||
|
||||
## Goal
|
||||
Integrate frontend auth UX with the external backend auth API as the single auth source. Do not authenticate directly against database from frontend code.
|
||||
|
||||
## Decision
|
||||
- Use backend REST auth endpoints under `/api/auth/*`.
|
||||
- Do not use direct frontend DB auth paths for production user auth.
|
||||
- Keep anonymous/local editing usable; enforce auth only for cloud/account features.
|
||||
|
||||
## User Stories
|
||||
- As a user, I can register and log in from UI.
|
||||
- As a returning user, I keep a valid session via refresh flow.
|
||||
- As a user, I can access organisation/workspace data after login.
|
||||
- As a product owner, cloud-only pages/actions require auth while local editor remains usable.
|
||||
|
||||
## Backend Contract (Current)
|
||||
- `POST /api/auth/user/login`
|
||||
- `POST /api/auth/user/register`
|
||||
- `GET /api/auth/refresh` (refresh token cookie-based)
|
||||
- `GET /api/auth/user/init`
|
||||
- `GET /api/auth/user/organisation` (Bearer backend JWT)
|
||||
- `GET /api/auth/user/workspaces` (Bearer backend JWT)
|
||||
|
||||
## Scope
|
||||
### Phase 1 (implement first)
|
||||
- Add auth API client module for backend routes.
|
||||
- Add auth state store (token + user + loading/error state).
|
||||
- Add pages:
|
||||
- `/auth/login`
|
||||
- `/auth/register`
|
||||
- Implement session bootstrap:
|
||||
- on app load call `/api/auth/refresh`
|
||||
- if valid, hydrate user/token
|
||||
- Add basic route guard for cloud/account routes only (not editor core local flow).
|
||||
|
||||
### Phase 2
|
||||
- Add logout flow and token-clear behavior.
|
||||
- Add auth-aware header/user menu.
|
||||
- Add organisation/workspace bootstrap after login.
|
||||
- Add error normalization and UI messaging from backend error shape.
|
||||
|
||||
### Phase 3
|
||||
- Add init check (`/api/auth/user/init`) onboarding path for first admin.
|
||||
- Add Google login route integration when backend mock is replaced.
|
||||
- Add E2E coverage for login/refresh/guard behavior.
|
||||
|
||||
## Non-Goals
|
||||
- Replacing local project save flow.
|
||||
- Direct DB access from frontend for identity/session.
|
||||
- Full RBAC/permission matrix in this phase.
|
||||
|
||||
## Technical Plan
|
||||
1. Create `lib/auth/api-client.ts` for backend endpoints and typed response mapping.
|
||||
2. Create `lib/auth/session.ts` (token memory + persistence policy + refresh logic).
|
||||
3. Build auth pages/forms with validation and backend error mapping.
|
||||
4. Add guarded route list for account/cloud screens.
|
||||
5. Add `Authorization: Bearer <backend JWT>` injection for protected API calls.
|
||||
6. Handle 401 with one refresh attempt, then force sign-in.
|
||||
|
||||
## Security Notes
|
||||
- Keep refresh token in httpOnly cookie (backend-managed).
|
||||
- Avoid storing refresh token in frontend storage.
|
||||
- If access token is stored client-side, prefer short-lived in-memory storage with minimal persistence.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Register/login works end-to-end against backend API.
|
||||
- Refresh flow restores session after reload when cookie is valid.
|
||||
- Protected cloud/account pages redirect to login when unauthenticated.
|
||||
- Organisation/workspace endpoints load successfully after auth.
|
||||
- Local-only editor route remains accessible without login.
|
||||
|
||||
## Risks and Mitigations
|
||||
- Risk: Auth guard accidentally blocks local editing.
|
||||
- Mitigation: explicit protected-route allowlist; default editor paths remain public.
|
||||
- Risk: Token mismatch/expiry causes noisy UX.
|
||||
- Mitigation: centralized fetch wrapper with refresh-once retry policy.
|
||||
- Risk: Backend error shape differences.
|
||||
- Mitigation: normalize `{ status, message, statusCode }` at API client boundary.
|
||||
|
||||
## Deliverables
|
||||
- Backend-auth-based login/register pages.
|
||||
- Auth client + session/refresh handling.
|
||||
- Protected routes for cloud/account features.
|
||||
- Updated header/session UX and test coverage baseline.
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Cloud Sync for Projects and Assets
|
||||
|
||||
## Goal
|
||||
Provide reliable sync of project state and media assets across devices/accounts.
|
||||
|
||||
## User Stories
|
||||
- As a logged-in user, I want my projects available on another device.
|
||||
- As a user, I want large media files to upload reliably and resume when interrupted.
|
||||
- As a user, I want local edits to sync automatically without losing data.
|
||||
|
||||
## Scope
|
||||
- Sync project JSON snapshots to backend storage.
|
||||
- Sync media assets to object storage.
|
||||
- Add conflict handling and sync status UI.
|
||||
- Keep offline-first local save as primary immediate write path.
|
||||
|
||||
## Data Model (Proposed)
|
||||
- `projects` table: `id`, `user_id`, `name`, `project_json`, `version`, timestamps.
|
||||
- `project_media` table: `project_id`, `asset_id`, `storage_key`, `checksum`, `size`, `mime`, timestamps.
|
||||
|
||||
## Technical Plan
|
||||
1. Backend API
|
||||
- Editor state endpoints (timeline, config, and project snapshot data):
|
||||
- `GET /api/editor/projects`
|
||||
- `GET /api/editor/projects/:id`
|
||||
- `PUT /api/editor/projects/:id` (optimistic concurrency)
|
||||
- File asset endpoints (reuse existing file service):
|
||||
- Base path: `/api/files`
|
||||
- Use existing endpoints for upload/list/download/metadata (`/api/files`, `/api/files/:id`, `/api/files/:id/download`, etc.)
|
||||
- Keep media upload/download concerns in file service; editor project payload stores file references (`fileId`/`key`) instead of raw binaries.
|
||||
|
||||
2. Client Sync Service
|
||||
- Hook into existing autosave flow after local save success.
|
||||
- Queue sync tasks (project snapshot, then missing media uploads).
|
||||
- Retry with backoff when offline/errors occur.
|
||||
|
||||
3. Upload Strategy
|
||||
- Use presigned multipart uploads for large files.
|
||||
- Track checksum to avoid re-upload unchanged assets.
|
||||
|
||||
4. Conflict Strategy (v1)
|
||||
- Version-based conflict detect.
|
||||
- If conflict, fetch latest and prompt user to choose local or remote.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Logged-in user can create project on device A and open on device B.
|
||||
- Media assets referenced by timeline are available after sync completes.
|
||||
- Failed uploads retry automatically and expose status.
|
||||
- No loss of local data when cloud is unavailable.
|
||||
|
||||
## Risks and Mitigations
|
||||
- Risk: Cost and latency for large asset storage.
|
||||
- Mitigation: Incremental uploads, dedupe by checksum, optional upload limits.
|
||||
- Risk: Conflict complexity.
|
||||
- Mitigation: Start with simple version conflict policy before merge logic.
|
||||
|
||||
## Deliverables
|
||||
- DB schema migrations and API endpoints.
|
||||
- Client cloud sync manager integrated with save lifecycle.
|
||||
- Sync status indicators and error recovery UX.
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
# Editor Backend API Spec (`/api/editor`)
|
||||
|
||||
## Purpose
|
||||
Provide a backend contract for storing and syncing editor project state (timeline, scenes, settings, metadata).
|
||||
|
||||
## Scope
|
||||
- Persist editor state as JSON snapshots.
|
||||
- Support optimistic concurrency for autosave sync.
|
||||
- Support project listing and retrieval across devices.
|
||||
|
||||
## Out of Scope (v1)
|
||||
- Real-time collaborative editing.
|
||||
- Server-side timeline merge.
|
||||
- Asset transcoding.
|
||||
|
||||
## Auth and Tenancy
|
||||
- All endpoints require `Authorization: Bearer <JWT>`.
|
||||
- All reads/writes are scoped to the authenticated user and workspace context.
|
||||
- Return `401` when JWT/context is invalid.
|
||||
- Return `404` when project does not exist in the caller workspace.
|
||||
|
||||
## Data Model
|
||||
|
||||
### `editor_projects`
|
||||
- `id` UUID PK
|
||||
- `workspace_id` string not null
|
||||
- `owner_id` bigint/int not null
|
||||
- `name` string(255) not null
|
||||
- `state_json` JSONB not null
|
||||
- `version` bigint not null default `1`
|
||||
- `created_at` timestamptz not null
|
||||
- `updated_at` timestamptz not null
|
||||
- `deleted_at` timestamptz nullable
|
||||
|
||||
Indexes:
|
||||
- `(workspace_id, updated_at desc)`
|
||||
- `(workspace_id, owner_id, updated_at desc)`
|
||||
- Optional GIN on `state_json` if querying inside JSON is needed later.
|
||||
|
||||
## Project State Envelope (stored in `state_json`)
|
||||
Use a versioned envelope so backend can validate shape at a high level while keeping timeline details client-owned.
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 3,
|
||||
"currentSceneId": "scene_main",
|
||||
"metadata": {
|
||||
"id": "proj_123",
|
||||
"name": "My Project",
|
||||
"duration": 42.5,
|
||||
"updatedAt": "2026-02-09T16:00:00.000Z"
|
||||
},
|
||||
"settings": {},
|
||||
"scenes": []
|
||||
}
|
||||
```
|
||||
|
||||
Validation (v1):
|
||||
- `schemaVersion` required integer.
|
||||
- `scenes` required array.
|
||||
- JSON payload size limit: recommend `<= 5 MB` (return `413` if exceeded).
|
||||
- Backend should not mutate timeline internals except metadata timestamps/version fields it owns.
|
||||
|
||||
## API Contract
|
||||
|
||||
Base path: `/api/editor`
|
||||
|
||||
### 1) List Projects
|
||||
`GET /api/editor/projects?offset=0&limit=20&search=my`
|
||||
|
||||
Query:
|
||||
- `offset` optional int `>= 0`, default `0`
|
||||
- `limit` optional int `1..100`, default `20`
|
||||
- `search` optional string (name contains)
|
||||
|
||||
Response `200`:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"workspaceId": "ws_123",
|
||||
"ownerId": 1,
|
||||
"name": "My Project",
|
||||
"version": 8,
|
||||
"updatedAt": "2026-02-09T16:00:00.000Z",
|
||||
"createdAt": "2026-02-08T16:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 2) Get Project
|
||||
`GET /api/editor/projects/:id`
|
||||
|
||||
Response `200`:
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"workspaceId": "ws_123",
|
||||
"ownerId": 1,
|
||||
"name": "My Project",
|
||||
"version": 8,
|
||||
"state": {},
|
||||
"updatedAt": "2026-02-09T16:00:00.000Z",
|
||||
"createdAt": "2026-02-08T16:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
Errors:
|
||||
- `404` project not found in workspace
|
||||
|
||||
### 3) Upsert Project State (Optimistic Concurrency)
|
||||
`PUT /api/editor/projects/:id`
|
||||
|
||||
Request:
|
||||
```json
|
||||
{
|
||||
"name": "My Project",
|
||||
"baseVersion": 8,
|
||||
"state": {},
|
||||
"clientRequestId": "optional-idempotency-key"
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
- If project does not exist, allow create when caller has write access. New record starts at `version = 1`.
|
||||
- If project exists, `baseVersion` must equal current `version`.
|
||||
- On success, increment version by 1 (or set to 1 for create), set `updated_at = now()`.
|
||||
- If `baseVersion` mismatch, return conflict.
|
||||
|
||||
Success `200`:
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"version": 9,
|
||||
"updatedAt": "2026-02-09T16:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
Conflict `409`:
|
||||
```json
|
||||
{
|
||||
"error": "VERSION_CONFLICT",
|
||||
"message": "Project has a newer version on server",
|
||||
"serverVersion": 10,
|
||||
"serverUpdatedAt": "2026-02-09T16:02:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
Validation errors:
|
||||
- `400` invalid payload
|
||||
- `413` payload too large
|
||||
|
||||
## Sync and Conflict Behavior (Client Contract)
|
||||
- Client reads project `version` from `GET /api/editor/projects/:id`.
|
||||
- Autosave sends `PUT` with `baseVersion`.
|
||||
- On `409`, client fetches latest server project and prompts user: keep local or server (v1 policy).
|
||||
- Do not silently overwrite newer server versions.
|
||||
|
||||
## Security and Limits
|
||||
- Enforce workspace isolation on every query.
|
||||
- Validate project ownership or workspace role before writes.
|
||||
- Rate limit write endpoint (`PUT`) to protect autosave storms.
|
||||
- Sanitize/validate `name` length and UTF-8 validity.
|
||||
|
||||
## Observability
|
||||
- Log structured fields: `workspaceId`, `projectId`, `userId`, `version`, `status`, latency.
|
||||
- Emit metrics:
|
||||
- `editor_api_put_success_total`
|
||||
- `editor_api_put_conflict_total`
|
||||
- `editor_api_put_validation_error_total`
|
||||
- `editor_api_payload_bytes`
|
||||
|
||||
## Suggested Backend Tasks
|
||||
1. Add DB migration for `editor_projects`.
|
||||
2. Implement `GET /api/editor/projects`.
|
||||
3. Implement `GET /api/editor/projects/:id`.
|
||||
4. Implement `PUT /api/editor/projects/:id` with optimistic concurrency transaction.
|
||||
5. Add request/response validation schema and shared error format.
|
||||
6. Add integration tests for auth, tenancy, conflict, payload size, and create/update flows.
|
||||
|
||||
## Acceptance Criteria
|
||||
- Project created on device A appears in list on device B.
|
||||
- Autosave updates increase `version` monotonically.
|
||||
- Concurrent writes produce deterministic `409` conflict responses.
|
||||
- Unauthorized cross-workspace access is blocked.
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
# Frontend-Backend Contract for Cloud Editor Sync
|
||||
|
||||
## Audience
|
||||
- Backend engineers who are not familiar with this frontend codebase.
|
||||
- Frontend engineers integrating cloud sync with `EditorCore`.
|
||||
|
||||
## Context: How the Frontend Editor Works
|
||||
- Frontend editor state is managed by a singleton `EditorCore`.
|
||||
- React components use `useEditor()` and read/write through editor managers.
|
||||
- Local save is offline-first and remains the immediate source of truth.
|
||||
- Cloud sync runs after local save and mirrors the same state to backend.
|
||||
|
||||
## What Backend Must Treat as Source of Truth
|
||||
- `state` payload sent by frontend is the canonical editor snapshot.
|
||||
- Backend stores it as opaque JSON (`state_json`) with light validation only.
|
||||
- Backend must not rewrite timeline internals (track layout, element ordering, etc).
|
||||
|
||||
## API Base Paths
|
||||
- Editor state API: `/api/editor`
|
||||
- File binary API: `/api/files` (already exists)
|
||||
|
||||
## TypeScript Contract (Shared Shapes)
|
||||
|
||||
```ts
|
||||
export type ISODateString = string;
|
||||
export type UUID = string;
|
||||
|
||||
export interface EditorProjectState {
|
||||
schemaVersion: number;
|
||||
currentSceneId: string;
|
||||
metadata: {
|
||||
id: string;
|
||||
name: string;
|
||||
duration: number;
|
||||
updatedAt: ISODateString;
|
||||
};
|
||||
settings: Record<string, unknown>;
|
||||
scenes: Array<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export interface EditorProjectListItem {
|
||||
id: UUID;
|
||||
workspaceId: string;
|
||||
ownerId: number;
|
||||
name: string;
|
||||
version: number;
|
||||
updatedAt: ISODateString;
|
||||
createdAt: ISODateString;
|
||||
}
|
||||
|
||||
export interface ListEditorProjectsResponse {
|
||||
items: EditorProjectListItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface GetEditorProjectResponse {
|
||||
id: UUID;
|
||||
workspaceId: string;
|
||||
ownerId: number;
|
||||
name: string;
|
||||
version: number;
|
||||
state: EditorProjectState;
|
||||
updatedAt: ISODateString;
|
||||
createdAt: ISODateString;
|
||||
}
|
||||
|
||||
export interface PutEditorProjectRequest {
|
||||
name: string;
|
||||
baseVersion: number;
|
||||
state: EditorProjectState;
|
||||
assetFileIds: UUID[];
|
||||
clientRequestId?: string;
|
||||
}
|
||||
|
||||
export interface PutEditorProjectResponse {
|
||||
id: UUID;
|
||||
version: number;
|
||||
updatedAt: ISODateString;
|
||||
}
|
||||
|
||||
export interface VersionConflictResponse {
|
||||
error: "VERSION_CONFLICT";
|
||||
message: string;
|
||||
serverVersion: number;
|
||||
serverUpdatedAt: ISODateString;
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoint Details
|
||||
|
||||
### `GET /api/editor/projects`
|
||||
Purpose:
|
||||
- Load project picker and recent projects list.
|
||||
|
||||
Query:
|
||||
- `offset` default `0`
|
||||
- `limit` default `20`, max `100`
|
||||
- `search` optional
|
||||
|
||||
Returns:
|
||||
- `200` with `ListEditorProjectsResponse`
|
||||
|
||||
### `GET /api/editor/projects/:id`
|
||||
Purpose:
|
||||
- Open project on app load or after conflict.
|
||||
|
||||
Returns:
|
||||
- `200` with `GetEditorProjectResponse`
|
||||
- `404` if not in workspace
|
||||
|
||||
### `PUT /api/editor/projects/:id`
|
||||
Purpose:
|
||||
- Autosave cloud snapshot with optimistic concurrency.
|
||||
|
||||
Request:
|
||||
- `PutEditorProjectRequest`
|
||||
|
||||
Returns:
|
||||
- `200` with `PutEditorProjectResponse`
|
||||
- `409` with `VersionConflictResponse` when `baseVersion` mismatches current
|
||||
- `400` invalid body
|
||||
- `401` invalid auth
|
||||
- `404` project/workspace not found
|
||||
- `413` payload too large
|
||||
|
||||
## Required Backend Semantics
|
||||
|
||||
### Optimistic concurrency
|
||||
- Compare `baseVersion` with current server `version`.
|
||||
- If equal: write state and increment version atomically.
|
||||
- If different: reject with `409 VERSION_CONFLICT`.
|
||||
|
||||
### Idempotency (recommended)
|
||||
- Respect `clientRequestId` for repeated retries of the same autosave.
|
||||
- If same `(projectId, clientRequestId)` is seen again, return prior success response.
|
||||
|
||||
### Transaction behavior
|
||||
- `PUT` should run in one transaction:
|
||||
1. Read current row with lock.
|
||||
2. Validate `baseVersion`.
|
||||
3. Update `name`, `state_json`, `version`, `updated_at`.
|
||||
4. Upsert `editor_project_assets` from `assetFileIds` (if implemented).
|
||||
|
||||
## File Service Integration (`/api/files`)
|
||||
|
||||
### Frontend upload sequence
|
||||
1. User adds media file in editor.
|
||||
2. Frontend calls existing `POST /api/files` with filename/contentType/size.
|
||||
3. Frontend uploads binary to returned `uploadUrl`.
|
||||
4. Frontend stores returned `file.id` in timeline element (`fileId`).
|
||||
5. Next autosave sends `assetFileIds` including that `fileId`.
|
||||
|
||||
### Backend expectations
|
||||
- Do not add editor-specific binary upload endpoint.
|
||||
- Validate `assetFileIds` belong to same workspace (strongly recommended).
|
||||
- Keep editor API and file API responsibilities separate.
|
||||
|
||||
## Frontend Sync Lifecycle (Backend Should Support)
|
||||
|
||||
### A) Open project
|
||||
1. Frontend requests `GET /api/editor/projects/:id`.
|
||||
2. Frontend hydrates `EditorCore` from `state`.
|
||||
3. Frontend stores returned `version` as `remoteVersion`.
|
||||
|
||||
### B) Autosave success path
|
||||
1. Local save completes.
|
||||
2. Frontend builds snapshot from `EditorCore`.
|
||||
3. Frontend calls `PUT` with `baseVersion = remoteVersion`.
|
||||
4. Backend returns new `version`.
|
||||
5. Frontend updates `remoteVersion`.
|
||||
|
||||
### C) Autosave conflict path
|
||||
1. Frontend sends `PUT`.
|
||||
2. Backend returns `409 VERSION_CONFLICT`.
|
||||
3. Frontend fetches latest `GET /api/editor/projects/:id`.
|
||||
4. Frontend prompts user: keep local or use server.
|
||||
5. Frontend resolves by re-saving chosen state with latest `baseVersion`.
|
||||
|
||||
## Non-Functional Requirements
|
||||
- P95 `PUT` latency target: under 300ms for normal payloads.
|
||||
- Support bursty autosave traffic (same project every few seconds).
|
||||
- Enforce per-user/workspace rate limiting without breaking normal autosave.
|
||||
|
||||
## Validation Rules (Minimum)
|
||||
- `name`: required, string, max 255
|
||||
- `baseVersion`: required, integer, `>= 0`
|
||||
- `state.schemaVersion`: required integer
|
||||
- `state.scenes`: required array
|
||||
- `assetFileIds`: required array (can be empty), each UUID format
|
||||
|
||||
## Error Format (Consistent Across Endpoints)
|
||||
```json
|
||||
{
|
||||
"error": "SOME_ERROR_CODE",
|
||||
"message": "Human-readable message"
|
||||
}
|
||||
```
|
||||
|
||||
Conflict is the only error that must include extra fields:
|
||||
```json
|
||||
{
|
||||
"error": "VERSION_CONFLICT",
|
||||
"message": "Project has a newer version on server",
|
||||
"serverVersion": 10,
|
||||
"serverUpdatedAt": "2026-02-09T16:02:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Backend Test Cases (Must Implement)
|
||||
1. Auth required: all `/api/editor/*` return `401` without valid JWT.
|
||||
2. Workspace isolation: project from another workspace returns `404`.
|
||||
3. Create/update path: `PUT` creates new and increments version on subsequent saves.
|
||||
4. Conflict path: stale `baseVersion` returns deterministic `409`.
|
||||
5. Payload validation: malformed `state` returns `400`.
|
||||
6. Payload size limit: oversized request returns `413`.
|
||||
7. File reference check: invalid cross-workspace `assetFileId` rejected.
|
||||
8. Idempotent retry (if enabled): duplicate `clientRequestId` returns same result.
|
||||
|
||||
## Copy/Paste Prompt for Backend Agent
|
||||
```text
|
||||
Use these two specs as the source of truth:
|
||||
- project-management/04a-editor-backend-spec.md
|
||||
- project-management/04b-editor-frontend-backend-contract.md
|
||||
|
||||
Implement `/api/editor` for cloud project sync with optimistic concurrency.
|
||||
Important: frontend editor state is an opaque JSON snapshot from EditorCore; do not rewrite timeline internals.
|
||||
|
||||
Build:
|
||||
1) Migrations:
|
||||
- editor_projects
|
||||
- editor_project_assets (recommended)
|
||||
2) Endpoints:
|
||||
- GET /api/editor/projects
|
||||
- GET /api/editor/projects/:id
|
||||
- PUT /api/editor/projects/:id
|
||||
3) Validation and error shapes exactly as documented.
|
||||
4) Workspace isolation + JWT auth on every endpoint.
|
||||
5) Integration with existing /api/files via file references only.
|
||||
6) Integration tests for auth, tenancy, concurrency conflict, validation, and size limits.
|
||||
|
||||
Return:
|
||||
- Migration files
|
||||
- Route handlers
|
||||
- Validation schemas
|
||||
- Tests
|
||||
- Short implementation notes for frontend consumers
|
||||
```
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"moduleResolution": "bundler",
|
||||
"module": "esnext"
|
||||
}
|
||||
}
|
||||
65
turbo.json
65
turbo.json
|
|
@ -1,65 +0,0 @@
|
|||
{
|
||||
"$schema": "https://turborepo.com/schema.json",
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
".next/**",
|
||||
"!.next/cache/**"
|
||||
],
|
||||
"env": [
|
||||
"NODE_ENV",
|
||||
"DATABASE_URL",
|
||||
"UPSTASH_REDIS_REST_URL",
|
||||
"UPSTASH_REDIS_REST_TOKEN",
|
||||
"MARBLE_WORKSPACE_KEY",
|
||||
"FREESOUND_CLIENT_ID",
|
||||
"FREESOUND_API_KEY",
|
||||
"MODAL_TRANSCRIPTION_URL"
|
||||
]
|
||||
},
|
||||
"check-types": {
|
||||
"dependsOn": [
|
||||
"^check-types"
|
||||
]
|
||||
},
|
||||
"dev": {
|
||||
"persistent": true,
|
||||
"cache": false
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": [
|
||||
"^lint"
|
||||
],
|
||||
"cache": false
|
||||
},
|
||||
"lint:fix": {
|
||||
"dependsOn": [
|
||||
"^lint:fix"
|
||||
],
|
||||
"cache": false
|
||||
},
|
||||
"format": {
|
||||
"dependsOn": [
|
||||
"^format"
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": [
|
||||
"^test"
|
||||
],
|
||||
"cache": false
|
||||
},
|
||||
"test:coverage": {
|
||||
"dependsOn": [
|
||||
"^test:coverage"
|
||||
],
|
||||
"outputs": [
|
||||
"coverage/**"
|
||||
],
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue