fix(render+studio): dev mock worker (unstick the queue) + lock predefined layers

Render — "stuck in Queued" fix:
- Jobs were created Queued and only a Windows AE node could claim them, so in the
  dev stack (no node) they queued forever.
- New devworker package: in-process mock worker drives Queued jobs through the steps
  with progress + live preview frames → Done. Enabled via RENDER_DEV_WORKER (default
  true in compose; set false in prod where real nodes claim jobs).
- db: DevClaimNextQueued (atomic oldest-queued → Preparing) + UpdateJobStepProgress
- Verified live: a stuck job advanced Preparing→Done in ~10s with frontend polling.

Studio — predefined template structure:
- Projects are always copied from a template; structure is fixed. Users customise
  existing layers, they don't add new ones.
- New studio-config flag ALLOW_ADD_LAYERS (false): StudioToolbar (add text/image/
  video/shape) returns null; SceneEditSidebar "add text layer" button hidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-05 22:10:05 +03:30
parent 81912cac66
commit 43d0e10543
7 changed files with 233 additions and 11 deletions
+4
View File
@@ -21,6 +21,7 @@ import {
import type { ShapeKind } from "@/lib/studio-layer-props";
import type { AddLayerInput } from "@/lib/studio-types";
import { useStudioStore } from "@/lib/studio-store";
import { ALLOW_ADD_LAYERS } from "@/lib/studio-config";
const SHAPE_OPTIONS: {
kind: ShapeKind;
@@ -94,6 +95,9 @@ export function StudioToolbar() {
const videoInputRef = useRef<HTMLInputElement>(null);
const [shapeOpen, setShapeOpen] = useState(false);
// Template projects have a predefined structure — adding new layers is disabled.
if (!ALLOW_ADD_LAYERS) return null;
const handleAddText = () => {
addLayer({
type: "text",
@@ -6,6 +6,7 @@ import { useTranslations } from "next-intl";
import { getTextProps, mergeLayerProps } from "@/lib/studio-layer-props";
import { useStudioStore } from "@/lib/studio-store";
import { ALLOW_ADD_LAYERS } from "@/lib/studio-config";
export function SceneEditSidebarContent() {
const t = useTranslations("auto.componentsStudioSidebarSceneEditSidebarContent");
@@ -137,17 +138,19 @@ export function SceneEditSidebarContent() {
)}
</div>
{/* Footer — add text button */}
<div className="shrink-0 border-t border-gray-200 p-3">
<button
type="button"
onClick={handleAddText}
className="flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-blue-300 bg-blue-50 px-3 py-2 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
>
<Plus className="h-3.5 w-3.5" aria-hidden />
{t("addTextLayer")}
</button>
</div>
{/* Footer — add text button (hidden for predefined template projects) */}
{ALLOW_ADD_LAYERS && (
<div className="shrink-0 border-t border-gray-200 p-3">
<button
type="button"
onClick={handleAddText}
className="flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-blue-300 bg-blue-50 px-3 py-2 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
>
<Plus className="h-3.5 w-3.5" aria-hidden />
{t("addTextLayer")}
</button>
</div>
)}
</div>
);
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Studio behaviour flags.
*
* FlatRender projects are always created FROM a template — the Studio service has
* no "blank project" concept and copies the template's scene graph into the editable
* project. The structure (scenes + layers) is therefore PREDEFINED: users customise
* existing layers (text, colours, images) but do not add or remove layers/scenes.
*
* Flip ALLOW_ADD_LAYERS to true only if a free-form (non-template) editor is ever
* introduced.
*/
/** Whether the user may add brand-new layers to the canvas. */
export const ALLOW_ADD_LAYERS = false;
/** Whether the user may add / delete / duplicate whole scenes. */
export const ALLOW_EDIT_SCENES = false;