rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled

rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)

Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
This commit was merged in pull request #61.
This commit is contained in:
2026-05-21 16:28:40 +02:00
committed by Hartmut
parent d9a7ec0338
commit b41c1d2501
943 changed files with 24545 additions and 16829 deletions
@@ -1,8 +1,8 @@
"use client";
import { useState, useMemo, useCallback } from "react";
import { FieldType } from "@capakraken/shared";
import type { BlueprintFieldDefinition, FieldOption, StaffingRequirement } from "@capakraken/shared";
import { FieldType } from "@nexus/shared";
import type { BlueprintFieldDefinition, FieldOption, StaffingRequirement } from "@nexus/shared";
import { trpc } from "~/lib/trpc/client.js";
import { RolePresetsEditor } from "./RolePresetsEditor.js";
import { FieldCard } from "./FieldCard.js";
@@ -48,10 +48,7 @@ interface FieldState {
// Helpers: Convert between FieldState and BlueprintFieldDefinition
// ---------------------------------------------------------------------------
function fieldDefToState(
def: BlueprintFieldDefinition,
target: BlueprintTargetValue,
): FieldState {
function fieldDefToState(def: BlueprintFieldDefinition, target: BlueprintTargetValue): FieldState {
const catalogField = findCatalogField(target, def.key);
if (catalogField) {
return {
@@ -186,9 +183,7 @@ export function BlueprintFieldCatalog({
// Build initial state from existing fieldDefs + catalog
// ---------------------------------------------------------------------------
const [catalogOverrides, setCatalogOverrides] = useState<
Record<string, FieldOverrides>
>(() => {
const [catalogOverrides, setCatalogOverrides] = useState<Record<string, FieldOverrides>>(() => {
const map: Record<string, FieldOverrides> = {};
// Start with all catalog fields disabled
for (const cf of catalog) {
@@ -269,21 +264,13 @@ export function BlueprintFieldCatalog({
// Handlers
// ---------------------------------------------------------------------------
const handleCatalogFieldChange = useCallback(
(key: string, overrides: FieldOverrides) => {
setCatalogOverrides((prev) => ({ ...prev, [key]: overrides }));
},
[],
);
const handleCatalogFieldChange = useCallback((key: string, overrides: FieldOverrides) => {
setCatalogOverrides((prev) => ({ ...prev, [key]: overrides }));
}, []);
const handleCustomFieldChange = useCallback(
(idx: number, overrides: FieldOverrides) => {
setCustomFields((prev) =>
prev.map((f, i) => (i === idx ? { ...f, overrides } : f)),
);
},
[],
);
const handleCustomFieldChange = useCallback((idx: number, overrides: FieldOverrides) => {
setCustomFields((prev) => prev.map((f, i) => (i === idx ? { ...f, overrides } : f)));
}, []);
function removeCustomField(idx: number) {
setCustomFields((prev) => prev.filter((_, i) => i !== idx));
@@ -370,9 +357,7 @@ export function BlueprintFieldCatalog({
// Collapsed categories
// ---------------------------------------------------------------------------
const [collapsedCategories, setCollapsedCategories] = useState<Set<string>>(
new Set(),
);
const [collapsedCategories, setCollapsedCategories] = useState<Set<string>>(new Set());
function toggleCategory(name: string) {
setCollapsedCategories((prev) => {
@@ -502,15 +487,16 @@ export function BlueprintFieldCatalog({
{/* Field cards */}
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-6">
{categories
.filter(
(cat) =>
activeCategory === null ||
activeCategory === cat.name,
)
.filter((cat) => activeCategory === null || activeCategory === cat.name)
.map((cat) => {
const fields = fieldsByCategory.get(cat.name) ?? [];
if (fields.length === 0 && searchQuery.trim()) return null;
if (fields.length === 0 && activeCategory !== null && activeCategory !== cat.name) return null;
if (
fields.length === 0 &&
activeCategory !== null &&
activeCategory !== cat.name
)
return null;
const isCollapsed = collapsedCategories.has(cat.name);
@@ -527,9 +513,7 @@ export function BlueprintFieldCatalog({
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
{cat.name}
</h3>
<span className="text-xs text-gray-400">
{cat.description}
</span>
<span className="text-xs text-gray-400">{cat.description}</span>
</button>
{!isCollapsed && (
<div className="grid grid-cols-1 gap-2">
@@ -538,9 +522,7 @@ export function BlueprintFieldCatalog({
key={field.key}
field={field}
overrides={catalogOverrides[field.key]!}
onChange={(ov) =>
handleCatalogFieldChange(field.key, ov)
}
onChange={(ov) => handleCatalogFieldChange(field.key, ov)}
/>
))}
{fields.length === 0 && (
@@ -555,8 +537,7 @@ export function BlueprintFieldCatalog({
})}
{/* Custom Fields section */}
{(activeCategory === null ||
activeCategory === "Custom Fields") && (
{(activeCategory === null || activeCategory === "Custom Fields") && (
<div>
<button
type="button"
@@ -564,9 +545,7 @@ export function BlueprintFieldCatalog({
className="flex items-center gap-2 mb-3 w-full text-left group"
>
<span className="text-xs text-gray-400 transition-transform group-hover:text-gray-600">
{collapsedCategories.has("Custom Fields")
? "\u25B6"
: "\u25BC"}
{collapsedCategories.has("Custom Fields") ? "\u25B6" : "\u25BC"}
</span>
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">
Custom Fields
@@ -585,8 +564,7 @@ export function BlueprintFieldCatalog({
label: cf.custom.label,
type: cf.custom.type,
category: "Custom Fields",
description:
cf.overrides.description || "Custom field",
description: cf.overrides.description || "Custom field",
...(cf.custom.options.length > 0
? { options: cf.custom.options }
: {}),
@@ -597,9 +575,7 @@ export function BlueprintFieldCatalog({
<FieldCard
field={pseudoCatalog}
overrides={cf.overrides}
onChange={(ov) =>
handleCustomFieldChange(idx, ov)
}
onChange={(ov) => handleCustomFieldChange(idx, ov)}
/>
<button
type="button"
@@ -619,19 +595,13 @@ export function BlueprintFieldCatalog({
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-gray-600">
Key{" "}
<span className="text-red-500">*</span>
Key <span className="text-red-500">*</span>
</label>
<input
type="text"
value={customKey}
onChange={(e) =>
setCustomKey(
e.target.value.replace(
/[^a-zA-Z0-9_]/g,
"",
),
)
setCustomKey(e.target.value.replace(/[^a-zA-Z0-9_]/g, ""))
}
placeholder="field_key"
className="app-input font-mono"
@@ -639,30 +609,21 @@ export function BlueprintFieldCatalog({
</div>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-gray-600">
Label{" "}
<span className="text-red-500">*</span>
Label <span className="text-red-500">*</span>
</label>
<input
type="text"
value={customLabel}
onChange={(e) =>
setCustomLabel(e.target.value)
}
onChange={(e) => setCustomLabel(e.target.value)}
placeholder="Display Label"
className="app-input"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-gray-600">
Type
</label>
<label className="text-xs font-medium text-gray-600">Type</label>
<select
value={customType}
onChange={(e) =>
setCustomType(
e.target.value as FieldType,
)
}
onChange={(e) => setCustomType(e.target.value as FieldType)}
className="app-input"
>
{FIELD_TYPES.map((ft) => (
@@ -677,9 +638,7 @@ export function BlueprintFieldCatalog({
<button
type="button"
onClick={addCustomField}
disabled={
!customKey.trim() || !customLabel.trim()
}
disabled={!customKey.trim() || !customLabel.trim()}
className={BTN_PRIMARY}
>
Add
@@ -704,8 +663,7 @@ export function BlueprintFieldCatalog({
onClick={() => setShowCustomForm(true)}
className="flex items-center gap-1.5 text-sm text-brand-600 hover:text-brand-800 font-medium py-2"
>
<span className="text-lg leading-none">+</span>{" "}
Add Custom Field
<span className="text-lg leading-none">+</span> Add Custom Field
</button>
)}
</div>
@@ -726,8 +684,7 @@ export function BlueprintFieldCatalog({
{/* Footer */}
<div className="flex items-center justify-between gap-3 px-6 py-4 border-t border-gray-200 dark:border-gray-700 shrink-0">
<span className="text-xs text-gray-400">
{enabledCount} field{enabledCount !== 1 ? "s" : ""} will be
saved
{enabledCount} field{enabledCount !== 1 ? "s" : ""} will be saved
</span>
<div className="flex items-center gap-3">
<button type="button" onClick={onClose} className={BTN_SECONDARY}>
@@ -747,8 +704,8 @@ export function BlueprintFieldCatalog({
) : (
<div className="px-6 py-4 overflow-y-auto">
<p className="text-xs text-gray-500 mb-4">
Role presets are auto-loaded in Step 3 of the Project Creation
Wizard when this blueprint is selected.
Role presets are auto-loaded in Step 3 of the Project Creation Wizard when this
blueprint is selected.
</p>
<RolePresetsEditor
initialPresets={initialRolePresets}
@@ -1,8 +1,8 @@
"use client";
import { useState } from "react";
import { FieldType } from "@capakraken/shared";
import type { BlueprintFieldDefinition, FieldOption, StaffingRequirement } from "@capakraken/shared";
import { FieldType } from "@nexus/shared";
import type { BlueprintFieldDefinition, FieldOption, StaffingRequirement } from "@nexus/shared";
import { trpc } from "~/lib/trpc/client.js";
import { RolePresetsEditor } from "./RolePresetsEditor.js";
@@ -53,9 +53,7 @@ function OptionsEditor({ options, onChange }: OptionsEditorProps) {
}
function updateOption(idx: number, field: "value" | "label", val: string) {
const next = options.map((o, i) =>
i === idx ? { ...o, [field]: val } : o,
);
const next = options.map((o, i) => (i === idx ? { ...o, [field]: val } : o));
onChange(next);
}
@@ -111,8 +109,7 @@ interface FieldRowProps {
function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
const [expanded, setExpanded] = useState(false);
const needsOptions =
field.type === FieldType.SELECT || field.type === FieldType.MULTI_SELECT;
const needsOptions = field.type === FieldType.SELECT || field.type === FieldType.MULTI_SELECT;
function update<K extends keyof BlueprintFieldDefinition>(
key: K,
@@ -126,9 +123,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
{/* Main row */}
<div className="flex flex-wrap items-center gap-2">
{/* Drag handle placeholder */}
<span className="text-gray-300 cursor-grab select-none text-lg leading-none">
</span>
<span className="text-gray-300 cursor-grab select-none text-lg leading-none"></span>
{/* Key */}
<input
@@ -158,7 +153,7 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
// Clear options when switching away from select types
const clearedOptions =
t === FieldType.SELECT || t === FieldType.MULTI_SELECT
? field.options ?? []
? (field.options ?? [])
: undefined;
onChange({ ...field, type: t, options: clearedOptions } as BlueprintFieldDefinition);
}}
@@ -218,29 +213,21 @@ function FieldRow({ field, onChange, onDelete }: FieldRowProps) {
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs text-gray-500 font-medium">
Placeholder
</label>
<label className="text-xs text-gray-500 font-medium">Placeholder</label>
<input
type="text"
value={field.placeholder ?? ""}
onChange={(e) =>
update("placeholder", e.target.value || undefined)
}
onChange={(e) => update("placeholder", e.target.value || undefined)}
placeholder="Placeholder text"
className="app-input"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs text-gray-500 font-medium">
Description
</label>
<label className="text-xs text-gray-500 font-medium">Description</label>
<input
type="text"
value={field.description ?? ""}
onChange={(e) =>
update("description", e.target.value || undefined)
}
onChange={(e) => update("description", e.target.value || undefined)}
placeholder="Helper text"
className="app-input"
/>
@@ -311,9 +298,8 @@ export function BlueprintFieldEditor({
const utils = trpc.useUtils();
const [activeTab, setActiveTab] = useState<"fields" | "presets">(initialTab);
const [fields, setFields] = useState<BlueprintFieldDefinition[]>(
() =>
[...initialFieldDefs].sort((a, b) => a.order - b.order),
const [fields, setFields] = useState<BlueprintFieldDefinition[]>(() =>
[...initialFieldDefs].sort((a, b) => a.order - b.order),
);
const [saveError, setSaveError] = useState<string | null>(null);
const [presetSaveError, setPresetSaveError] = useState<string | null>(null);
@@ -327,17 +313,11 @@ export function BlueprintFieldEditor({
}
function removeField(idx: number) {
setFields((prev) =>
prev
.filter((_, i) => i !== idx)
.map((f, i) => ({ ...f, order: i })),
);
setFields((prev) => prev.filter((_, i) => i !== idx).map((f, i) => ({ ...f, order: i })));
}
function updateField(idx: number, updated: BlueprintFieldDefinition) {
setFields((prev) =>
prev.map((f, i) => (i === idx ? updated : f)),
);
setFields((prev) => prev.map((f, i) => (i === idx ? updated : f)));
}
function handleSave() {
@@ -375,8 +355,7 @@ export function BlueprintFieldEditor({
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">
Edit Fields:{" "}
<span className="text-gray-600 font-normal">{blueprintName}</span>
Edit Fields: <span className="text-gray-600 font-normal">{blueprintName}</span>
</h2>
<button
type="button"
@@ -461,7 +440,8 @@ export function BlueprintFieldEditor({
) : (
<div className="px-6 py-4">
<p className="text-xs text-gray-500 mb-4">
Role presets are auto-loaded in Step 3 of the Project Creation Wizard when this blueprint is selected.
Role presets are auto-loaded in Step 3 of the Project Creation Wizard when this
blueprint is selected.
</p>
<RolePresetsEditor
initialPresets={initialRolePresets}
@@ -2,8 +2,8 @@
import { useState, useEffect } from "react";
import type { FormEvent } from "react";
import type { BlueprintTarget } from "@capakraken/shared";
import type { BlueprintFieldDefinition } from "@capakraken/shared";
import type { BlueprintTarget } from "@nexus/shared";
import type { BlueprintFieldDefinition } from "@nexus/shared";
import { trpc } from "~/lib/trpc/client.js";
import { BlueprintFieldCatalog } from "./BlueprintFieldCatalog.js";
import { useSelection } from "~/hooks/useSelection.js";
@@ -637,7 +637,7 @@ export function BlueprintsClient() {
}
initialRolePresets={
Array.isArray(editingBlueprint.rolePresets)
? (editingBlueprint.rolePresets as import("@capakraken/shared").StaffingRequirement[])
? (editingBlueprint.rolePresets as import("@nexus/shared").StaffingRequirement[])
: []
}
initialTab={editingTab}
@@ -1,8 +1,8 @@
"use client";
import { useState } from "react";
import { FieldType } from "@capakraken/shared";
import type { FieldOption } from "@capakraken/shared";
import { FieldType } from "@nexus/shared";
import type { FieldOption } from "@nexus/shared";
import type { CatalogField } from "~/lib/blueprint-field-catalog.js";
// ---------------------------------------------------------------------------
@@ -234,9 +234,7 @@ function DefaultValueInput({
<input
type="number"
value={value != null ? String(value) : ""}
onChange={(e) =>
onChange(e.target.value === "" ? undefined : Number(e.target.value))
}
onChange={(e) => onChange(e.target.value === "" ? undefined : Number(e.target.value))}
placeholder="No default"
className="app-input"
/>
@@ -247,9 +245,7 @@ function DefaultValueInput({
<input
type="date"
value={typeof value === "string" ? value : ""}
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
className="app-input"
/>
);
@@ -258,9 +254,7 @@ function DefaultValueInput({
return (
<select
value={typeof value === "string" ? value : ""}
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
className="app-input"
>
<option value="">No default</option>
@@ -286,9 +280,7 @@ function DefaultValueInput({
<input
type="url"
value={typeof value === "string" ? value : ""}
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
placeholder="https://..."
className="app-input"
/>
@@ -299,9 +291,7 @@ function DefaultValueInput({
<input
type="email"
value={typeof value === "string" ? value : ""}
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
placeholder="name@example.com"
className="app-input"
/>
@@ -311,9 +301,7 @@ function DefaultValueInput({
return (
<textarea
value={typeof value === "string" ? value : ""}
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
placeholder="No default"
className="app-input resize-none"
rows={2}
@@ -325,9 +313,7 @@ function DefaultValueInput({
<input
type="text"
value={typeof value === "string" ? value : ""}
onChange={(e) =>
onChange(e.target.value === "" ? undefined : e.target.value)
}
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
placeholder="No default"
className="app-input"
/>
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import type { StaffingRequirement } from "@capakraken/shared";
import type { StaffingRequirement } from "@nexus/shared";
import { uuid } from "~/lib/uuid.js";
function makeEmptyPreset(): StaffingRequirement {