feat: project cover art with AI generation, branding rename, RBAC fix, computation graph

- Add DALL-E cover art generation for projects (Azure OpenAI + standard OpenAI)
- CoverArtSection component with generate/upload/remove/focus-point controls
- Client-side image compression (10MB input → WebP/JPEG, max 1920px)
- DALL-E settings in admin panel (deployment, endpoint, API key)
- MCP assistant tools for cover art (generate_project_cover, remove_project_cover)
- Rename "Planarchy" → "plANARCHY" across all UI-facing text (13 files)
- Fix hardcoded canEdit={true} on project detail page — now checks user role
- Computation graph visualization (2D/3D) for calculation rules
- OG image and OpenGraph metadata

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-18 11:31:56 +01:00
parent 21af720f90
commit 093e13b88f
86 changed files with 5623 additions and 744 deletions
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
import { trpc } from "~/lib/trpc/client.js";
type EffortUnitMode = "per_frame" | "per_item" | "flat";
@@ -184,7 +185,7 @@ export function EffortRulesClient() {
<div className="mb-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label className="mb-1 block text-xs font-medium text-gray-500 uppercase">Name</label>
<label className="mb-1 flex items-center text-xs font-medium text-gray-500 uppercase">Name <InfoTooltip content="A descriptive name for this rule set, e.g. 'CGI Standard Rules'. Used to identify the set when linking it to estimates." /></label>
<input
value={editing.name}
onChange={(e) => setEditing({ ...editing, name: e.target.value })}
@@ -193,7 +194,7 @@ export function EffortRulesClient() {
/>
</div>
<div>
<label className="mb-1 block text-xs font-medium text-gray-500 uppercase">Description</label>
<label className="mb-1 flex items-center text-xs font-medium text-gray-500 uppercase">Description <InfoTooltip content="Optional notes about when this rule set should be used." /></label>
<input
value={editing.description}
onChange={(e) => setEditing({ ...editing, description: e.target.value })}
@@ -210,7 +211,7 @@ export function EffortRulesClient() {
onChange={(e) => setEditing({ ...editing, isDefault: e.target.checked })}
className="rounded border-gray-300"
/>
Default rule set (auto-selected for new estimates)
Default rule set (auto-selected for new estimates) <InfoTooltip content="When checked, this set is pre-selected when creating new estimates. Only one set can be default." />
</label>
{/* Rules table */}
@@ -234,11 +235,11 @@ export function EffortRulesClient() {
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-gray-200 text-xs uppercase tracking-wider text-gray-500">
<th className="py-2 pr-2 font-medium">Scope type</th>
<th className="px-2 py-2 font-medium">Discipline</th>
<th className="px-2 py-2 font-medium">Chapter</th>
<th className="px-2 py-2 font-medium">Unit mode</th>
<th className="px-2 py-2 text-right font-medium">Hours/unit</th>
<th className="py-2 pr-2 font-medium"><span className="flex items-center">Scope type <InfoTooltip content="The type of deliverable this rule applies to: Shot, Asset, Environment, Sequence, or Other." /></span></th>
<th className="px-2 py-2 font-medium"><span className="flex items-center">Discipline <InfoTooltip content="The production discipline (e.g. 3D Animation, Compositing) that this rule generates demand for." /></span></th>
<th className="px-2 py-2 font-medium"><span className="flex items-center">Chapter <InfoTooltip content="Optional grouping within a discipline. Used to organize demand lines in the estimate staffing tab." /></span></th>
<th className="px-2 py-2 font-medium"><span className="flex items-center">Unit mode <InfoTooltip content="How hours are calculated: 'Per frame' multiplies by frame count, 'Per item' by item count, 'Flat' is a fixed amount." /></span></th>
<th className="px-2 py-2 text-right font-medium"><span className="flex items-center justify-end">Hours/unit <InfoTooltip content="The number of hours per unit. Combined with the unit mode and scope item count, this pre-fills the total effort in estimates." /></span></th>
<th className="pl-2 py-2 font-medium w-10"></th>
</tr>
</thead>
@@ -392,11 +393,11 @@ export function EffortRulesClient() {
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-gray-200 text-xs uppercase tracking-wider text-gray-500">
<th className="py-2 pr-3 font-medium">Scope type</th>
<th className="px-3 py-2 font-medium">Discipline</th>
<th className="px-3 py-2 font-medium">Chapter</th>
<th className="px-3 py-2 font-medium">Unit mode</th>
<th className="pl-3 py-2 text-right font-medium">Hours/unit</th>
<th className="py-2 pr-3 font-medium"><span className="flex items-center">Scope type <InfoTooltip content="The deliverable type (Shot, Asset, etc.) this rule targets." /></span></th>
<th className="px-3 py-2 font-medium"><span className="flex items-center">Discipline <InfoTooltip content="The production discipline this demand line is for." /></span></th>
<th className="px-3 py-2 font-medium"><span className="flex items-center">Chapter <InfoTooltip content="Optional sub-grouping within the discipline." /></span></th>
<th className="px-3 py-2 font-medium"><span className="flex items-center">Unit mode <InfoTooltip content="Per frame, per item, or flat hours calculation mode." /></span></th>
<th className="pl-3 py-2 text-right font-medium"><span className="flex items-center justify-end">Hours/unit <InfoTooltip content="Hours multiplied by the scope item count to compute total effort." /></span></th>
</tr>
</thead>
<tbody>