fix: make project template inheritance granular and safe
CI/CD / test_and_check_app (push) Failing after 3m8s
CI/CD / Publish immutable image (push) Skipped
CI/CD / test_and_check_app (pull_request) Failing after 3m8s
CI/CD / Publish immutable image (pull_request) Skipped

This commit is contained in:
Nico Schröder
2026-09-08 10:25:52 +00:00
committed by Twyco
parent fed6a282a4
commit 9895fc36db
50 changed files with 1504 additions and 624 deletions
@@ -50,10 +50,6 @@ const switchTemplateForm = useForm({
)?.id,
});
const resetTemplateForm = useForm({
confirmation_key: '',
});
const deleteForm = useForm({
confirmation_key: '',
});
@@ -76,10 +72,6 @@ const canSwitchTemplate = computed(
switchTemplateForm.source_template_id !== undefined,
);
const isResetConfirmationValid = computed(
() => resetTemplateForm.confirmation_key === props.project.key,
);
const isDeleteConfirmationValid = computed(
() => deleteForm.confirmation_key === props.project.key,
);
@@ -89,19 +81,12 @@ const submitSwitchTemplate = (): void => {
return;
}
switchTemplateForm.post(ProjectController.switchTemplate.url(props.project.id), {
preserveScroll: true,
});
};
const submitResetTemplate = (): void => {
if (!isResetConfirmationValid.value) {
return;
}
resetTemplateForm.post(ProjectController.resetTemplate.url(props.project.id), {
preserveScroll: true,
});
switchTemplateForm.post(
ProjectController.switchTemplate.url(props.project.id),
{
preserveScroll: true,
},
);
};
const submitDelete = (): void => {
@@ -209,56 +194,84 @@ const submitDelete = (): void => {
<CardHeader>
<CardTitle>Template baseline</CardTitle>
<CardDescription>
Switch this project to another published template or reset it back to the currently linked template baseline. Both actions can overwrite local configuration.
Switch this project to another published template. Use
the inheritance panels to reset individual settings to
the currently linked template.
</CardDescription>
</CardHeader>
<CardContent class="space-y-6">
<div class="grid gap-4 md:grid-cols-2">
<div class="border-border/70 rounded-xl border p-4">
<p class="text-muted-foreground text-xs font-semibold uppercase tracking-[0.18em]">
<p
class="text-muted-foreground text-xs font-semibold uppercase tracking-[0.18em]"
>
Current linked template
</p>
<p class="text-foreground mt-2 text-sm font-semibold">
{{ currentTemplate?.name ?? 'No linked template' }}
<p
class="text-foreground mt-2 text-sm font-semibold"
>
{{
currentTemplate?.name ??
'No linked template'
}}
</p>
<p class="text-muted-foreground mt-2 text-sm">
{{
templateState?.inheritsFromTemplate
? 'Inherited and overridden areas stay visible after switching or resetting.'
? 'Inherited and overridden areas stay visible after switching.'
: 'This project currently behaves like a standalone baseline.'
}}
</p>
</div>
<div class="border-border/70 rounded-xl border p-4">
<p class="text-muted-foreground text-xs font-semibold uppercase tracking-[0.18em]">
<p
class="text-muted-foreground text-xs font-semibold uppercase tracking-[0.18em]"
>
Warning
</p>
<p class="text-foreground mt-2 text-sm font-medium">
Template changes can replace board workflow, ticket metadata, repositories, and notifications for copied areas.
Switching templates can replace inherited board
workflow, ticket metadata, repositories, and
notifications.
</p>
<p class="text-muted-foreground mt-2 text-sm">
Confirm each action with the exact project key to avoid accidental overwrites.
Confirm each action with the exact project key
to avoid accidental overwrites.
</p>
</div>
</div>
<div class="grid gap-6 xl:grid-cols-2">
<form class="space-y-4 rounded-xl border p-4" @submit.prevent="submitSwitchTemplate">
<div class="grid gap-6">
<form
class="space-y-4 rounded-xl border p-4"
@submit.prevent="submitSwitchTemplate"
>
<div class="space-y-1">
<h3 class="text-sm font-semibold">Switch to another template</h3>
<h3 class="text-sm font-semibold">
Switch to another template
</h3>
<p class="text-muted-foreground text-sm">
Applying a different template replaces the copied areas with that template's current baseline.
Applying a different template replaces the
copied areas with that template's current
baseline.
</p>
</div>
<div class="grid gap-2">
<Label for="source_template_id">Published template</Label>
<Label for="source_template_id"
>Published template</Label
>
<select
id="source_template_id"
v-model="switchTemplateForm.source_template_id"
:disabled="switchTemplateForm.processing || availableSwitchTemplates.length === 0"
v-model="
switchTemplateForm.source_template_id
"
:disabled="
switchTemplateForm.processing ||
availableSwitchTemplates.length === 0
"
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px]"
>
<option :value="undefined" disabled>
@@ -272,68 +285,54 @@ const submitDelete = (): void => {
{{ template.name }}
</option>
</select>
<InputError :message="switchTemplateForm.errors.source_template_id" />
<InputError
:message="
switchTemplateForm.errors
.source_template_id
"
/>
</div>
<div class="grid gap-2">
<Label for="switch_confirmation_key">Confirmation key</Label>
<Label for="switch_confirmation_key"
>Confirmation key</Label
>
<Input
id="switch_confirmation_key"
v-model="switchTemplateForm.confirmation_key"
v-model="
switchTemplateForm.confirmation_key
"
:disabled="switchTemplateForm.processing"
placeholder="Type the project key exactly"
/>
<InputError :message="switchTemplateForm.errors.confirmation_key" />
<InputError
:message="
switchTemplateForm.errors
.confirmation_key
"
/>
</div>
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div
class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
>
<p class="text-muted-foreground text-sm">
Existing project-specific configuration in copied areas may be replaced or removed.
Existing project-specific configuration in
copied areas may be replaced or removed.
</p>
<Button
type="submit"
variant="outline"
:disabled="switchTemplateForm.processing || !canSwitchTemplate"
:disabled="
switchTemplateForm.processing ||
!canSwitchTemplate
"
>
Switch template
</Button>
</div>
</form>
<form class="space-y-4 rounded-xl border p-4" @submit.prevent="submitResetTemplate">
<div class="space-y-1">
<h3 class="text-sm font-semibold">Reset to current template baseline</h3>
<p class="text-muted-foreground text-sm">
Re-apply the currently linked template and clear local divergence inside copied areas.
</p>
</div>
<div class="grid gap-2">
<Label for="reset_confirmation_key">Confirmation key</Label>
<Input
id="reset_confirmation_key"
v-model="resetTemplateForm.confirmation_key"
:disabled="resetTemplateForm.processing || !currentTemplate"
placeholder="Type the project key exactly"
/>
<InputError :message="resetTemplateForm.errors.confirmation_key" />
</div>
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p class="text-muted-foreground text-sm">
This restores the current template baseline for copied areas and rewinds project-level overrides there.
</p>
<Button
type="submit"
variant="outline"
:disabled="resetTemplateForm.processing || !isResetConfirmationValid || !currentTemplate"
>
Reset to baseline
</Button>
</div>
</form>
</div>
</CardContent>
</Card>