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
@@ -4,6 +4,7 @@ import { useState } from "react";
import { trpc } from "~/lib/trpc/client.js";
import { DateInput } from "~/components/ui/DateInput.js";
import { SkillTagInput } from "~/components/ui/SkillTagInput.js";
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
export function StaffingPanel() {
const [requiredSkills, setRequiredSkills] = useState<string[]>(["TypeScript", "React"]);
@@ -41,7 +42,7 @@ export function StaffingPanel() {
<div className="app-surface max-w-xl p-4">
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">How scoring works</p>
<p className="mt-1 text-sm text-gray-500">
Planarchy blends skill fit, free capacity, cost, and current utilization. Add the must-have skills first, then narrow the date window to get cleaner results.
plANARCHY blends skill fit, free capacity, cost, and current utilization. Add the must-have skills first, then narrow the date window to get cleaner results.
</p>
</div>
</div>
@@ -54,7 +55,7 @@ export function StaffingPanel() {
<div className="mt-6 space-y-5">
<div>
<label className="app-label">Required Skills</label>
<label className="app-label">Required Skills<InfoTooltip content="Skills the candidate must have. The engine scores overlap and proficiency against this list." /></label>
<SkillTagInput
value={requiredSkills}
onChange={setRequiredSkills}
@@ -84,7 +85,7 @@ export function StaffingPanel() {
</div>
<div>
<label className="app-label">Hours per Day</label>
<label className="app-label">Hours per Day<InfoTooltip content="Required hours per day for the role. Used to check conflicts and estimate capacity." /></label>
<input
type="number"
value={hoursPerDay}
@@ -150,16 +151,16 @@ export function StaffingPanel() {
</div>
</div>
<div className="rounded-2xl border border-brand-200 bg-brand-50 px-4 py-3 text-right dark:border-brand-900/50 dark:bg-brand-900/20">
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-brand-700 dark:text-brand-200">Match Score</div>
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-brand-700 dark:text-brand-200 inline-flex items-center gap-0.5">Match Score<InfoTooltip content="Composite score (0-100) blending skill fit, free capacity, cost efficiency, and current utilization." /></div>
<div className="mt-1 text-3xl font-semibold text-brand-700 dark:text-brand-100">{suggestion.score}</div>
</div>
</div>
<div className="mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
<ScoreBar label="Skills" value={suggestion.scoreBreakdown.skillScore} />
<ScoreBar label="Availability" value={suggestion.scoreBreakdown.availabilityScore} />
<ScoreBar label="Cost" value={suggestion.scoreBreakdown.costScore} />
<ScoreBar label="Utilization" value={suggestion.scoreBreakdown.utilizationScore} />
<ScoreBar label="Skills" value={suggestion.scoreBreakdown.skillScore} tooltip="Quality of skill overlap with the requested stack, weighted by proficiency level." />
<ScoreBar label="Availability" value={suggestion.scoreBreakdown.availabilityScore} tooltip="Free capacity during the selected period, accounting for existing bookings and vacations." />
<ScoreBar label="Cost" value={suggestion.scoreBreakdown.costScore} tooltip="Cost efficiency based on the resource's LCR relative to the team average." />
<ScoreBar label="Utilization" value={suggestion.scoreBreakdown.utilizationScore} tooltip="Current workload. Higher score means more capacity available (lower utilization)." />
</div>
<div className="mt-4 flex flex-wrap gap-2">
@@ -205,10 +206,10 @@ export function StaffingPanel() {
);
}
function ScoreBar({ label, value }: { label: string; value: number }) {
function ScoreBar({ label, value, tooltip }: { label: string; value: number; tooltip?: string }) {
return (
<div className="rounded-2xl border border-gray-200 bg-gray-50/70 p-3 dark:border-gray-700 dark:bg-gray-900/40">
<div className="mb-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-gray-500">{label}</div>
<div className="mb-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-gray-500 inline-flex items-center gap-0.5">{label}{tooltip && <InfoTooltip content={tooltip} />}</div>
<div className="h-2 rounded-full bg-gray-200/80 dark:bg-gray-800">
<div
className="h-full rounded-full bg-brand-500"